Подпрограммы, формальные и фактические параметры — различия между версиями
Juliet (обсуждение | вклад) (Новая страница: «= Подпрограммы = Подпрограммы используются для решения однотипных задач. В первую очередь…») |
Ulysses (обсуждение | вклад) м (Ulysses переименовал страницу Формальные и фактические параметры подпрограмм в Подпрограммы, формальные и фактические параметры: лучш…) |
||
(не показано 10 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
= Подпрограммы = | = Подпрограммы = | ||
− | Подпрограммы используются для решения однотипных задач | + | Подпрограммы используются для решения однотипных задач и позволяют структурировать код. |
− | + | Если блок кода решает какую-то общую задачу (например, поиск минимума из трех чисел или сортировка массива), стоит выделить его в подпрограмму. | |
+ | |||
+ | Во-первых, это облегчит понимание вашего кода. Вызов подпрограммы с хорошим именем лучше отражает логику программы. Во-вторых, подпрограммы позволяют избавиться от дублирования кода. Если вы поймали себя на том, что второй или даже третий раз делаете «копи/паст», значит пришло время написать подпрограмму. | ||
== Процедуры и функции == | == Процедуры и функции == | ||
Строка 9: | Строка 11: | ||
В Pascal выделяют два типа подпрограмм: '''процедуры''' и '''функции'''. Они похожи, но функции имеют возвращаемое значение (например, функция <tt>min</tt> или <tt>cos</tt>), а процедуры — нет (<tt>write</tt>, <tt>read</tt>). | В Pascal выделяют два типа подпрограмм: '''процедуры''' и '''функции'''. Они похожи, но функции имеют возвращаемое значение (например, функция <tt>min</tt> или <tt>cos</tt>), а процедуры — нет (<tt>write</tt>, <tt>read</tt>). | ||
+ | <u>Пример процедуры.</u> Печать на консоль пары значений: <tt>x, f(x)</tt>. | ||
+ | <source lang="Pascal"> | ||
+ | procedure printArgFuncPair(x: real; fx: real); | ||
+ | begin | ||
+ | writelnFormat('x = {0,6:f4}; f(x) = {1,6:f4}', | ||
+ | x, fx); | ||
+ | end; | ||
+ | </source> | ||
+ | |||
+ | <u>Пример функции.</u> Минимум из двух целых чисел. | ||
+ | <source lang="Pascal"> | ||
+ | function minInt(x, y: integer): integer; | ||
+ | begin | ||
+ | if x < y then | ||
+ | result := x | ||
+ | else | ||
+ | result := y; | ||
+ | end; | ||
+ | </source> | ||
+ | |||
+ | == Параметры подпрограмм == | ||
+ | |||
+ | Логически выделяют три типа параметров: | ||
+ | * входные; | ||
+ | * входно-выходные; | ||
+ | * выходные. | ||
+ | |||
+ | Как следует из названия, '''входные''' параметры только используются подпрограммой. В процессе работы подпрограммы они не изменяются. Например, функция <tt>cos(x)</tt> получает на ''вход'' значение угла в радианах и ''возвращает'' значение косинуса этого угла. Значение угла никак не модифицируется. То же можно сказать о параметрах подпрограмм из примеров выше. | ||
+ | |||
+ | '''Входно-выходные''' параметры используются в подпрограмме и модифицируются. То есть новое значение параметра важно для вызывающей стороны. Вспомним, например, процедуру инкремента: | ||
+ | |||
+ | <source lang="Pascal"> | ||
+ | procedure inc(var x: integer); | ||
+ | begin | ||
+ | x := x + 1; | ||
+ | end; | ||
+ | </source> | ||
+ | |||
+ | И рассмотрим вызов процедуры в основной программе: | ||
+ | |||
+ | <source lang="Pascal"> | ||
+ | begin | ||
+ | var x := 5; | ||
+ | writeln(x); | ||
+ | inc(x); | ||
+ | writeln(x); | ||
+ | end. | ||
+ | </source> | ||
+ | |||
+ | В результате работы получится следующий вывод: | ||
+ | <pre> | ||
+ | 5 | ||
+ | 6 | ||
+ | </pre> | ||
+ | |||
+ | То есть после вызова процедуры изменилось значение нашей переменной <tt>x</tt> из основной программы. <br /> | ||
+ | ''Входно-выходные'' параметры описываются с ключевым словом '''<tt>var</tt>'''. | ||
+ | |||
+ | В чем разница между «обычным» описанием параметра и описанием с ключевым словом '''<tt>var</tt>'''? Если мы просто описываем параметр подпрограммы: | ||
+ | <source lang="Pascal"> | ||
+ | function f(x: integer): integer; | ||
+ | </source> | ||
+ | Это значит, что при вызове подпрограммы значение '''фактического''' параметра (то есть, например, значение <tt>5</tt> при вызове <tt>f(5)</tt>) будет скопировано «в формальный параметр» <tt>x</tt>. Это можно понимать так: представьте что вы — специалист по сборке моделей самолетов. Друг просит вас собрать модель из запчастей, которые у него есть. Но свои запчасти он вам не дает! Поэтому кто-то (может быть его папа) делает такие же точно запчасти и передает их вам. Вы собираете модель самолета и отдаете ее другу. | ||
+ | |||
+ | А теперь опишем наши запчасти с ключевым словом '''<tt>var</tt>'''. | ||
+ | <source lang="Pascal"> | ||
+ | procedure p(var x: integer); | ||
+ | </source> | ||
+ | Вот теперь, когда друг попросит собрать ему самолет, вы соберете модель непосредственно из его запчастей. И его запчасти чудесным образом превратятся в замечательный самолет! | ||
+ | |||
+ | Итак, вернемся к программированию. Описание формального параметра без ключевого слова означает, что при вызове подпрограммы значение фактического параметра будет ''скопировано'' и подставлено на место формального. | ||
+ | То есть при вызове процедуры <tt>p</tt> | ||
+ | <source lang="Pascal"> | ||
+ | procedure p(x: integer); | ||
+ | </source> | ||
+ | следующим образом: | ||
+ | <source lang="Pascal"> | ||
+ | var y := 5; | ||
+ | p(y); | ||
+ | </source> | ||
+ | Значение 5 будет скопировано, и внутри процедуры <tt>p</tt> переменная <tt>x</tt> будет равна 5. | ||
+ | |||
+ | А если мы описываем формальный параметр с ключевым словом '''<tt>var</tt>''', то при вызове подпрограммы в качестве формального параметра ''будет использован сам фактический параметр''. То есть для процедуры <tt>q</tt> | ||
+ | <source lang="Pascal"> | ||
+ | procedure q(var x: integer); | ||
+ | </source> | ||
+ | при вызове | ||
+ | <source lang="Pascal"> | ||
+ | var y := 5; | ||
+ | q(y); | ||
+ | </source> | ||
+ | процедура <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>. Заметим, что теперь написать <tt>incGood(5);</tt> мы не можем. В качестве входно-выходного параметра может выступать только переменная. | ||
+ | |||
+ | '''Выходные''' параметры отличаются от входно-выходных только тем, что их начальное значение нас не интересует. Описываются точно так же, как и входно-выходные — с ключевым словом '''<tt>var</tt>'''. | ||
+ | |||
+ | Пример: | ||
+ | <source lang="Pascal"> | ||
+ | procedure calcBounds(x: real; var left, right: integer); | ||
+ | begin | ||
+ | left := Floor(x); | ||
+ | right := Ceil(x); | ||
+ | end; | ||
+ | begin | ||
+ | var z := 1.5; | ||
+ | var l, r: integer; | ||
+ | calcBounds(z, l, r); | ||
+ | writeln(l, ' | ', z, ' | ', r); | ||
+ | end. | ||
+ | </source> | ||
+ | Результат: | ||
+ | <pre> | ||
+ | 1 | 1.5 | 2 | ||
+ | </pre> | ||
[[Категория:Основы программирования]] | [[Категория:Основы программирования]] |
Текущая версия на 19:27, 23 октября 2014
Подпрограммы
Подпрограммы используются для решения однотипных задач и позволяют структурировать код.
Если блок кода решает какую-то общую задачу (например, поиск минимума из трех чисел или сортировка массива), стоит выделить его в подпрограмму.
Во-первых, это облегчит понимание вашего кода. Вызов подпрограммы с хорошим именем лучше отражает логику программы. Во-вторых, подпрограммы позволяют избавиться от дублирования кода. Если вы поймали себя на том, что второй или даже третий раз делаете «копи/паст», значит пришло время написать подпрограмму.
Процедуры и функции
В 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. Заметим, что теперь написать incGood(5); мы не можем. В качестве входно-выходного параметра может выступать только переменная.
Выходные параметры отличаются от входно-выходных только тем, что их начальное значение нас не интересует. Описываются точно так же, как и входно-выходные — с ключевым словом var.
Пример:
procedure calcBounds(x: real; var left, right: integer);
begin
left := Floor(x);
right := Ceil(x);
end;
begin
var z := 1.5;
var l, r: integer;
calcBounds(z, l, r);
writeln(l, ' | ', z, ' | ', r);
end.
Результат:
1 | 1.5 | 2