Два основных способа передачи параметров подпрограммам

Любая вызываемая подпрограмма перед началом своей работы должна получить от вызывающего её модуля (главной программы, или подпрограммы) некоторый набор начальных (входных) значений и, завершив работу,  передать этому модулю сформированное ею результирующее (выходное) значение, или набор таких значений (например, массив).

Таким образом, использование подпрограмм предполагает наличие механизма  информационного взаимодействия (обмена) между вызывающим и вызываемым модулями.

Главной формой такого механизма в современных ЯП  выступает аппарат формальных – фактических параметров, суть которого состоит в следующем.

Исходные данные, которые необходимо обработать в подпрограмме, а так же информацию о том, куда подпрограмма должна записать окончательные результаты этой обработки готовятся в вызывающем модуле в виде, так называемого, списка фактических параметров.

Список фактических параметров обычно является частью синтаксической конструкции вызова подпрограммы, в которой помимо этого списка должно быть указано так же и её имя.

Например, если в главной программе описаны переменные  A, B, Mx и переменные A и B инициализированы (т.е. имеют присвоенные им значения), то конструкция вызова подпрограммы вычисления наибольшего из значений A и B с присваиванием результата этого вычисления  переменной Mx,  могла бы иметь вид оператора вызова процедуры

Max(A,B,Mx); 

В указанной конструкции Max – имя вызываемой подпрограммы (она обязательно должна быть предварительно определена), а A,B,Mx – передаваемый этой подпрограмме список фактических параметров.

Не все фактические параметры в этом списке однородны: A, B – предоставляют подпрограмме входные значения, Mx – это переменная программы, в которую подпрограмма должна записать выходное значение.

Для корректной реализации данного вычисления, подпрограмма Max в Паскале могла  бы быть устроена, например, так: 

Procedure Max(X,Y: Integer; var Z: Integer);

Begin

   Z:=Y;

   If X>Y then Z:=X

End;

В этом определении (описании) подпрограммы Max особую роль, с точки зрения информационного взаимодействия с вызывающей программой, играет заголовок:

Procedure Max(X,Y: Integer; var Z: Integer);

В заголовке задан список спецификаций (описаний) так называемых, формальных параметров данной подпрограммыX, Y, Z, которые строго в порядке следования, в момент вызова подпрограммы, сопоставляются с указанными в конструкции вызова фактическими параметрами. Таким образом, в нашем примере, устанавливается соответствие: A – X,  B – Y, Mx – Z.

Причём, принципиальным здесь моментом является то, что один из формальных параметров (а именно, тот, который соответствует выходному значению!) помечен ключевым словом var.

Рассмотрим теперь подробно смысл формальных параметров и особенности их информационного взаимодействия с фактическими параметрами. Здесь следует чётко различать два способа передачи параметров подпрограммам.

Передача параметров подпрограме по значению.

Формальные параметры, не помеченные меткой var называются параметрами-значениями. Каждый такой параметр представлен в заголовке подпрограммы идентификатором и является, по существу локальной (т.е. внутренней) переменной этой подпрограммы.

В момент вызова подпрограммы, в параметры-значения копируются значения соответствующих им фактических параметров (которые могут быть представлены в вызывающем модуле любыми выражениями соответствующего типа).

Так, если в нашем примере, A=7, B=9, то в сразу же после  вызова Max(A+10,B,Mx) формальным параметрам X и Y автоматически будут присвоены входные значения, соответственно, 17 и 9, с которыми подпрограмма и начнёт свою работу.

Другими словами, формальные параметры-значеия – это локальные переменные подпрограммы, которые в момент её старта инициализируются копиями значений соответствующих им фактических параметров вызова данной подпргораммы. Дальше работа с параметрами-значениями в теле подпрограммы идёт как обычная работа с её локальными переменными.

Из этого определения, в частности, следует, что никакие изменения текущих значений формального параметра-значения в теле подпрограммы не могут влиять на  значения соответствующего фактического параметра. Т.е. в этом случае фактические параметры, даже если они – переменные, гарантированно защищены от изменений со стороны подпрограммы. Действительно, как изменения в копии могут повлиять на оригинал? Никак.

Данный способ информационной связи фактических-формальных параметров называется передачей параметров по значению

Передача параметров подпрограме по ссылке (передача параметров-переменных).

Формальные параметры, помеченные меткой var называются параметрами-переменными. Каждый такой параметр представлен в заголовке подпрограммы идентификатором и является, по существу локальным в подпрограмме именем соответствующего фактического параметра, который обязательно должен быть переменной.

В момент вызова подпрограммы, её формальные параметры-переменные замещаются адресами соответствующих им фактических параметров, так что подпрограмма получает доступ непосредственно к самим этим переменным, находящимся в адресном пространстве вызывающей программы. Т.е. подпрограмме на этот раз не просто передаётся копия - значение переменной, а полностью доверяется оригинал, т.е. она сама.

Получив адрес переменной (ссылку на неё), подпрограмма может как воспользоваться текущим значением этой переменной как входным, так и (что более важно!) записать в эту переменную результат своей работы.

В данном случае любые изменения значений формального параметра-переменной в теле работающей подпрограммы означают на самом деле изменения соответствующего фактического параметра.

Этот способ информационной связи фактических-формальных параметров обычно используется для получения выходных значений подпрограмм и называется передачей параметров по ссылке или передачей параметров-переменных.

В нашем примере, в вызове подпрограммы: Max(A+10,B,Mx) первые два параметра передаются ей по значению, а третий – по ссылке. Именно через третий параметр мы и получаем результат.  

Особенность передачи параметров-массивов.

В принципе, всё выше изложенное на примере подпрограммы Max, работающей с целыми, справедливо и для любых типов данных. Однако, соображения эффективности могут вносить некоторые коррективы.

Рассмотрим, например, подпрограмму выбора максимального элемента в массиве, определяемом  следующими условиями:

Const lim=50000;

Type TArray= array[1..lim] of Integer;

Заголовок этой подпрограммы, с точки зрения логики (ведь ни сам масив, ни его длина, в процессе поиска максимума не должны бы меняться) мог бы иметь, например, вид:  

procedure ArrMax( a: TArray; N: Word; var Mx: Integer);

Действительно, такой заголовок, при реализации соответствующей подпрограммы, позволит, например, в основной программе, для обработки некоторого её массива b записать что-то вроде :

ArrMax(b, 40, maximum);

получив в переменной maximum ответ для 40-элементного начального отрезка масива b и при этом, гарантировано оставив  неизменным сам  входной массива b. Вроде бы, всё хорошо.

Но посмотрим на это решение с точки зрения расходов памяти.

  1. Т.к. в основной программе должно присутствовать описание var b: TArray; , то в её адресном пространстве будет резервировано место для 50000 элементов массива b.
  2. Т.к. первый параметр процедуры передаётся по значению, в адресном пространстве подпрограммы будет дополнительно резервировано столько же места для формального параметра a (в который для обработки будет передаваться копия оригинального массива b).

Итого, нам в программе понадобится память под  100 000  элементов.

Немного подправим заголовок:

procedure ArrMax( var a: TArray; N: Word; var Mx: Integer);

Что изменилось? А то, что теперь первый параметр передаётся по ссылке. Вместо копии массива, подпрограмма теперь получила ссылку на наш оригинальный массив b и может с ним работать (переименовав его как a) напрямую.

Т.е. теперь программа гораздо более эффективна как по расходу памяти (сыкономили 50000 слов), так и по времени (не тратится время на копирование массива b в a). По-моему, совсем неплохо. Запомните этот приём.

И закончим эту статью полной реализацией простой  задачи, иллюстрирующей оба рассмотренных способа передачи параметров.

Задача. Написать Паскаль-программу выбора наибольшего из трёх заданных целочисленных значений.

  1. program Max_Of_3;
  2.    var a,b,c,r1,r2:Integer;
  3.    procedure Max(X,Y: Integer; var Z: Integer);
  4.    begin
  5.       Z:=X;
  6.       If Y › Z Then Z:=Y
  7.    End;
  8. Begin
  9.    ReadLn(a,b,c);
  10.    Max(a,b,r1);
  11.    Max(c,r1,r2);
  12.    Writeln('Max(',a,',',b,',',c,')=',r2)
  13. End.

4.833335
Your rating: Нет Average: 4.8 (12 votes)

Комментарии

Вопросы к экзамену, думаю

Вопросы к экзамену, думаю опубликовать в ближайший понедельник.

Удивительно, но на момент

Удивительно, но на момент когда число просмотров  данной статьи перевалило за  полусотню, ни один из посетителей так и не откоментировал (не заметил?) явную неточность, содержащуюся в следующей выбранной из статьи цитате:

Исходные данные, которые необходимо обработать в подпрограмме, а так же
информацию о том, куда подпрограмма должна записать окончательные
результаты этой обработки, готовятся в вызываемом модуле в виде, так называемого, списка фактических параметров.

В.Ш. о каком именно

В.Ш. о каком именно понедельнике вы говорили?
их много.... Уточните пожалуйста дату!!! :)

Неточность в том, что в

Неточность в том, что в подпрограмме обрабатываются либо исходные данные, либо их дубликаты?

Уважаемый, Бондарев

Уважаемый, Бондарев Александр! Когда человек в пятницу, да и в любой другой день пишет о " ближайшем понедельнике", он вправе надеяться что его поймут однозначно, потому что ближайший понедельник всегда ОДИН!

Нет, Наташа. Вникните в

Нет, Наташа. Вникните в приведённую цитату насколько это возможно!

Ой... В вызываемом модуле

Ой... В вызываемом модуле формальные параметры, а в вызывающем фактические. Значит вызываемом нужно исправить на вызывающем?

Вот именно, Наташа! Не прошло

Вот именно, Наташа! Не прошло и недели ... Впрочем, в основном тексте уже исправлено.

А можно было бы использовать

А можно было бы использовать var не только перед массивами, но и перед переменными, если в подпрограмме они остаются неизменными, для того, чтобы сэкономить место? На сколько это целесообразно?

Можно, но зачем? 1. В

Можно, но зачем?
1. В подпрограмме всё равно резервируется слово, но теперь уже не для приёма значения, а для приёма адреса фактического параметра. Так что для передаваемых простых типов экономии никакой (если так передаётся параметр, занимающий байт, то даже наоборот расход увеличится - адресу нужно места больше чем байт).
2. Плохой стиль, падение понятности и надёжности. Программа, передавая значение переменной по ссылке не застрахована от того, что это значение в результате работы подпрограммы (например, из-за ошибки в ней или из-за особенностей реализации) неожиданно не изменится. Чтобы получить гарантию автору программы придётся изучать код подпрограммы, который не всегда доступен. Ради чего все эти муки?

Спасибо, что разъяснили. А

Спасибо, что разъяснили.
А вот если задача состоит в том, чтобы написать процедуру, которая находит индексы нечетных элементов одномерного массива. Эти индексы стоит занести в массив или сделать вывод в процедуре?
Конечно, легче было бы сразу их выводить. Но Вы уже говорили, что лучше ввод-вывод организовывать в вызывающей программе. Я правильно понимаю, что таким образом улучшается универсальность подпрограммы? Т.е. эти индексы можно потом будет использовать и для других целей.
Но как поступать в таких "мелких" задачах? Что будет являться хорошим стилем программирования?

В принципе, Вы сами всё

В принципе, Вы сами всё правильно рассудили. Жёстких правил, конечно нет и мои предписания по поводу ввода-вывода в подпрограммах, нося обшерекомендательный характер, безусловно, можно и нужно в некоторых случаях нарушать.
Что же касается техники накопления в подпрограмме больших по объёму данных, то здесь часто используют связные структуры данных, о которых мы ещё будем говорить.

Олифир Павел аватар

Program NetProbelam;

  1. Program NetProbelam;
  2. Procedure Probel(S: String;var BezProb: String);
  3. var i: integer;
  4.   begin
  5.     bezprob:='';
  6.     for I := 1 to length(s) do
  7.           if s[i]<>' ' then bezprob:=bezprob+s[i];
  8.   end;
  9. var s,Bs: string;
  10.   begin
  11.      write('Stroka= ');
  12.      readln(s);
  13.      Probel(s,bs);
  14.      write('Stroka bez probelov= ',bs);
  15.      readln;
  16.   end.

Валерий Шахамболетович приблизительно так надо было решить?

Да, можно так, но ...

В точном соответствии с условием, я бы решил иначе:

  1. Procedure DelSpaces(var s: String);
  2.   var i: Byte;
  3. begin
  4.   i:=pos(' ',s);
  5.   While i<>0 Do Begin
  6.      Delete(s,i,1);
  7.      i:=pos(' ',s);
  8.   End;

Олифир Павел аватар

Рекурсия

Все ясно, спасибо за разъяснение Валерий Шахамболетович. А если мы решили использовать рекурсию в этом примере она бы выглядела так? (основываясь на вашем примере)

  1.  Procedure DelSpaces(var s: String);
  2.    var i: Byte;
  3.  begin
  4.    i:=pos(' ',s);
  5.    if i>0 then
  6.       begin
  7.          Delete(s,i,1);
  8.          delspaces(s)
  9.       end
  10.  end;

Думаю, сработает.

Думаю, сработает. Каждый рекурсивный перевызов будет удалять по одному пробелу из представленной переменной входной строки, которая поочерёдно передаётся по ссылке всем таким перевызовам.
Но такие (изменяющие входные данные) решения при использовании рекурсии зачастую чреваты трудно обнаруживаемыми ошибками.
Гораздо надёжней золотое правило рекурсии - никогда не изменять входных данных, а просто "конструировать" на их основе результат:

  1. function  delspaces(s:String):String;
  2.    var  i: Byte;
  3. begin
  4.    i:=pos(' ',s);
  5.    if i=0 then delspaces:=s
  6.    Else begin
  7.          delspaces:=Copy(s,1, i -1) + delspaces(Copy(s, i+1, Length(s))
  8.     end
  9. end;

Валерий Шахамболетович, может

Валерий Шахамболетович, может не здесь надо это спрашивать, но на сегодняшнем занятии мне остался непонятен такой вопрос: в алгоритме, где надо было поменять строки местами была такая строка

  1. WritelnMtr(lim,lim,a)

что она значит? и почему Writeln и Mtr слитно?

Ну почему же не надо?

Очень даже надо, НЕОБХОДИМО спрашивать, Вика!
Речь шла о следующем.
Т.к. часто приходится построчно выводить матрицу (в нашей классной задаче - дважды), хорошо бы не дублировать этот код, а придумать и вызывать процедуру, которая умеет это делать.
Вот мы и предположили наличие в нашей программе такой процедуры (не разместив её там из-за недостатка времени), например, с заголовком: procedure WritelnMTR(m,n:Byte; var a: MTR). Она печатает заданную матрицу a в m строк и n столбцов. Её легко реализовать, но в классе мы ограничились только её вызовами.
Так что, слитно, поскольку WritelnMtr - это придуманное нами имя, а не имя стандартной процедуры.

теперь понятно, спасибо:-)

теперь понятно, спасибо:-)

Дзюба Дмитрий аватар

Валерий Шахамболетович, можно

Валерий Шахамболетович, можно ли домашнюю задачу про всевозможные перестановки 4 букв сделать через строки?

Да, Дмитрий, это естественно.

Да, Дмитрий, это естественно.

Юрий Жаворонков аватар

Статья

Хм, про передачу параметров по значению - знал, а вот про передачу по ссылке - в новинку.
Действительно второй метод в некоторых задачах/ситуациях обходится куда дешевле и по ресурсам, и по времени.
+ как альтернатива или замена (для некоторых задач) функциям.

ОК, Юрий.

Не передав ссылки в Паскале результат работы подпрограммы и не вернешь через параметр вызываемомуму модулю.