Подпрограммы, формальные и фактические параметры — различия между версиями

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Параметры подпрограмм)
(Параметры подпрограмм)
Строка 42: Строка 42:
 
Как следует из названия, '''входные''' параметры только используются подпрограммой. В процессе работы подпрограммы они не изменяются. Например, функция <tt>cos(x)</tt> получает на ''вход'' значение угла в радианах и ''возвращает'' значение косинуса этого угла. Значение угла никак не модифицируется. То же можно сказать о параметрах подпрограмм из примеров выше.
 
Как следует из названия, '''входные''' параметры только используются подпрограммой. В процессе работы подпрограммы они не изменяются. Например, функция <tt>cos(x)</tt> получает на ''вход'' значение угла в радианах и ''возвращает'' значение косинуса этого угла. Значение угла никак не модифицируется. То же можно сказать о параметрах подпрограмм из примеров выше.
  
Входно-выходные параметры используются в подпрограмме и модифицируются. То есть новое значение параметра важно для вызывающей стороны. Вспомним, например, процедуру инкремента:
+
'''Входно-выходные''' параметры используются в подпрограмме и модифицируются. То есть новое значение параметра важно для вызывающей стороны. Вспомним, например, процедуру инкремента:
  
 
<source lang="Pascal">
 
<source lang="Pascal">
Строка 75: Строка 75:
 
function f(x: integer): integer;
 
function f(x: integer): integer;
 
</source>
 
</source>
Это значит, что при вызове подпрограммы значение '''фактического''' параметра (то есть, например, значение <tt>5</tt> при вызове <tt>f(5)</tt>) будет скопировано «в формальный параметр» <tt>x</tt>. Это можно понимать так: представьте что вы — специалист по сборке моделей самолетов. Друг просит вас собрать модель из запчастей, которые у него есть. Но свои запчасти он вам не дает! Поэтому кто-то (может быть его папа) делает такие же точно запчасти и передает их вам. Вы собираете модель самолета и отдаете ее папе друга. А папа отдает сыну.
+
Это значит, что при вызове подпрограммы значение '''фактического''' параметра (то есть, например, значение <tt>5</tt> при вызове <tt>f(5)</tt>) будет скопировано «в формальный параметр» <tt>x</tt>. Это можно понимать так: представьте что вы — специалист по сборке моделей самолетов. Друг просит вас собрать модель из запчастей, которые у него есть. Но свои запчасти он вам не дает! Поэтому кто-то (может быть его папа) делает такие же точно запчасти и передает их вам. Вы собираете модель самолета и отдаете ее другу.
  
 
А теперь опишем наши запчасти с ключевым словом '''<tt>var</tt>'''.  
 
А теперь опишем наши запчасти с ключевым словом '''<tt>var</tt>'''.  
Строка 83: Строка 83:
 
Вот теперь, когда друг попросит собрать ему самолет, вы соберете модель непосредственно из его запчастей. И его запчасти чудесным образом превратятся в замечательный самолет!
 
Вот теперь, когда друг попросит собрать ему самолет, вы соберете модель непосредственно из его запчастей. И его запчасти чудесным образом превратятся в замечательный самолет!
  
Итак, если вернуться к программированию. Описание формального параметра без ключевого слова означает, что при вызове подпрограммы значение фактического параметра будет ''скопировано'' и подставлено на место формального.
+
Итак, вернемся к программированию. Описание формального параметра без ключевого слова означает, что при вызове подпрограммы значение фактического параметра будет ''скопировано'' и подставлено на место формального.
 
То есть при вызове процедуры <tt>p</tt>
 
То есть при вызове процедуры <tt>p</tt>
 
<source lang="Pascal">
 
<source lang="Pascal">
Строка 93: Строка 93:
 
p(y);
 
p(y);
 
</source>
 
</source>
Значение 5 будет скопировано и внутри процедуры <tt>p</tt> переменная <tt>x</tt> будет равна 5.
+
Значение 5 будет скопировано, и внутри процедуры <tt>p</tt> переменная <tt>x</tt> будет равна 5.
  
 
А если мы описываем формальный параметр с ключевым словом '''<tt>var</tt>''', то при вызове подпрограммы в качестве формального параметра ''будет использован сам фактический параметр''. То есть для процедуры <tt>q</tt>
 
А если мы описываем формальный параметр с ключевым словом '''<tt>var</tt>''', то при вызове подпрограммы в качестве формального параметра ''будет использован сам фактический параметр''. То есть для процедуры <tt>q</tt>
Строка 106: Строка 106:
 
процедура <tt>q</tt> будет работать не с копией <tt>y</tt>, а с оригиналом, самим <tt>y</tt>. И если внутри процедуры меняется значение <tt>x</tt>, значит меняется и сам <tt>y</tt>.
 
процедура <tt>q</tt> будет работать не с копией <tt>y</tt>, а с оригиналом, самим <tt>y</tt>. И если внутри процедуры меняется значение <tt>x</tt>, значит меняется и сам <tt>y</tt>.
  
Такая передача параметров называется '''передачей по ссылке''' (в первом же случае мы говорили о '''передаче параметра по значению'''). «Физически» это означает, что в подпрограмму передается ''адрес'' фактического параметра, и тогда подпрограмма работает непосредственно с этим фактическим параметром.
+
Такая передача параметров называется '''передачей по ссылке''' (в первом же случае мы говорим о '''передаче параметра по значению'''). «Физически» это означает, что в подпрограмму передается ''адрес'' фактического параметра, и тогда подпрограмма работает непосредственно с этим фактическим параметром.
 +
 
 +
'''Замечание.''' Важно понимать, что тип параметра в первую очередь играет роль для ''вызывающей стороны''! Внутри подпрограммы и с входными, и с входно-выходными параметрами работают одинаково. Внутри подпрограммы вы можете делать с ними все, что хотите. Вопрос в том, как это влияет на того, кто вызвал данную подпрограмму.
 +
 
 +
Рассмотрим пример неправильной процедуры инкремента:
 +
<source lang="Pascal">
 +
procedure incBad(x: integer);
 +
begin
 +
  writeln('incBad begin');
 +
  writeln('x before = ', x);
 +
  x := x + 1;
 +
  writeln('x after = ', x);
 +
  writeln('end incBad');
 +
end;
 +
</source>
 +
И вызовем её в основной программе следующим образом:
 +
<source lang="Pascal">
 +
begin
 +
  var y := 5;
 +
  writeln('y before = ', y);
 +
  incBad(y);
 +
  writeln('y after = ', y);
 +
end.
 +
</source>
 +
Получим вывод:
 +
<pre>
 +
y before = 5
 +
incBad begin
 +
x before = 5
 +
x after = 6
 +
end incBad
 +
y after = 5
 +
</pre>
 +
 
 +
Видим, что внутри процедуры переменная <tt>x</tt> ведет себя самым обычным образом. Мы прибавили к ней единицу, и её значение изменилось. Но на переменную <tt>y</tt> процедура <tt>incBad</tt> никак не повлияла. Все потому, что формальный параметр <tt>x</tt> — входной, он передается по значению. То есть значение <tt>y</tt> было скопировано при вызове процедуры, но сама переменная <tt>y</tt> «не пострадала». С тем же успехом мы могли бы написать <tt>incBad(5);</tt>
 +
 
 +
А теперь рассмотрим правильную процедуру:
 +
<source lang="Pascal">
 +
procedure incGood(var x: integer);
 +
begin
 +
  writeln('incGood begin');
 +
  writeln('x before = ', x);
 +
  x := x + 1;
 +
  writeln('x after = ', x);
 +
  writeln('end incGood');
 +
end;
 +
</source>
 +
И вызовем её в основной программе:
 +
<source lang="Pascal">
 +
begin
 +
  var y := 5;
 +
  writeln('y before = ', y);
 +
  incGood(y);
 +
  writeln('y after = ', y);
 +
end.
 +
</source>
 +
Получим вывод:
 +
<pre>
 +
y before = 5
 +
incGood begin
 +
x before = 5
 +
x after = 6
 +
end incGood
 +
y after = 6
 +
</pre>
 +
 
 +
Видим, что теперь изменилось и значение нашей переменной <tt>y</tt>. Все правильно, ведь теперь <tt>x</tt> — входно-выходной параметр и передается по ссылке. Все, что мы сделали с <tt>x</tt> в процедуре, мы на самом деле проделали с переменной <tt>y</tt>.
  
 
[[Категория:Основы программирования]]
 
[[Категория:Основы программирования]]

Версия 21:15, 28 октября 2012

Подпрограммы

Подпрограммы используются для решения однотипных задач. В первую очередь, они позволяют избавиться от дублирования кода. Если вы поймали себя на том, что второй или даже третий раз делаете «копи/паст», значит пришло время написать подпрограмму.

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

Подпрограммы имеют:

  • имя;
  • список параметров.

Процедуры и функции

В Pascal выделяют два типа подпрограмм: процедуры и функции. Они похожи, но функции имеют возвращаемое значение (например, функция min или cos), а процедуры — нет (write, read).

Пример процедуры. Печать на консоль пары значений: x, f(x).

procedure printArgFuncPair(x: real; fx: real); 
begin
  writelnFormat('x = {0,6:f4}; f(x) = {1,6:f4}', 
    x, fx);
end;

Пример функции. Минимум из двух целых чисел.

function MinInt(x, y: integer): integer;
begin
  if x < y then
    result := x
  else
    result := y;
end;

Параметры подпрограмм

Логически выделяют три типа параметров:

  • входные;
  • входно-выходные;
  • выходные.

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

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

procedure inc(var x: integer);
begin
  x := x + 1;
end;

И рассмотрим вызов процедуры в основной программе:

begin
  var x := 5;
  writeln(x);
  inc(x);
  writeln(x);
end.

В результате работы получится следующий вывод:

5
6

То есть после вызова процедуры изменилось значение нашей переменной x из основной программы.
Входно-выходные параметры описываются с ключевым словом var.

В чем разница между «обычным» описанием параметра и описанием с ключевым словом var? Если мы просто описываем параметр подпрограммы:

function f(x: integer): integer;

Это значит, что при вызове подпрограммы значение фактического параметра (то есть, например, значение 5 при вызове f(5)) будет скопировано «в формальный параметр» x. Это можно понимать так: представьте что вы — специалист по сборке моделей самолетов. Друг просит вас собрать модель из запчастей, которые у него есть. Но свои запчасти он вам не дает! Поэтому кто-то (может быть его папа) делает такие же точно запчасти и передает их вам. Вы собираете модель самолета и отдаете ее другу.

А теперь опишем наши запчасти с ключевым словом var.

procedure p(var x: integer);

Вот теперь, когда друг попросит собрать ему самолет, вы соберете модель непосредственно из его запчастей. И его запчасти чудесным образом превратятся в замечательный самолет!

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

procedure p(x: integer);

следующим образом:

var y := 5;
p(y);

Значение 5 будет скопировано, и внутри процедуры p переменная x будет равна 5.

А если мы описываем формальный параметр с ключевым словом var, то при вызове подпрограммы в качестве формального параметра будет использован сам фактический параметр. То есть для процедуры q

procedure q(var x: integer);

при вызове

var y := 5;
q(y);

процедура q будет работать не с копией y, а с оригиналом, самим y. И если внутри процедуры меняется значение x, значит меняется и сам y.

Такая передача параметров называется передачей по ссылке (в первом же случае мы говорим о передаче параметра по значению). «Физически» это означает, что в подпрограмму передается адрес фактического параметра, и тогда подпрограмма работает непосредственно с этим фактическим параметром.

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

Рассмотрим пример неправильной процедуры инкремента:

procedure incBad(x: integer);
begin
  writeln('incBad begin');
  writeln('x before = ', x);
  x := x + 1;
  writeln('x after = ', x);
  writeln('end incBad');
end;

И вызовем её в основной программе следующим образом:

begin
  var y := 5;
  writeln('y before = ', y);
  incBad(y);
  writeln('y after = ', y);
end.

Получим вывод:

y before = 5
incBad begin
x before = 5
x after = 6
end incBad
y after = 5

Видим, что внутри процедуры переменная x ведет себя самым обычным образом. Мы прибавили к ней единицу, и её значение изменилось. Но на переменную y процедура incBad никак не повлияла. Все потому, что формальный параметр x — входной, он передается по значению. То есть значение y было скопировано при вызове процедуры, но сама переменная y «не пострадала». С тем же успехом мы могли бы написать incBad(5);

А теперь рассмотрим правильную процедуру:

procedure incGood(var x: integer);
begin
  writeln('incGood begin');
  writeln('x before = ', x);
  x := x + 1;
  writeln('x after = ', x);
  writeln('end incGood');
end;

И вызовем её в основной программе:

begin
  var y := 5;
  writeln('y before = ', y);
  incGood(y);
  writeln('y after = ', y);
end.

Получим вывод:

y before = 5
incGood begin
x before = 5
x after = 6
end incGood
y after = 6

Видим, что теперь изменилось и значение нашей переменной y. Все правильно, ведь теперь x — входно-выходной параметр и передается по ссылке. Все, что мы сделали с x в процедуре, мы на самом деле проделали с переменной y.