Процедуры и функции
Процедуры и функции в программах предназначены для того, чтобы
заменять часто повторяющиеся фрагменты в программе. В самом деле, нудно
переписывать раз за разом одни и те же операторы. Гораздо лучше обозвать их
каким-либо именем, и когда они очередной раз потребуются, вместо них
поставить это имя. Такой "именованный блок операторов" называется процедурой.
Функции оформляются почти так же, как и процедуры, но используются для
вычисления какого-либо одного значения, которое называется результатом функции.
Поэтому функция вызывается не как оператор, а как операнд в выражении.
Процедурой, например, является ClrScr (очистка экрана). Она выполняет все
действия, необходимые для того, чтобы стереть с экрана всю информацию. Для
очистки экрана в программе нам необязательно писать операторы для этих действий.
Нам даже неважно, в чем эти действия заключаются. Достаточно написать ClrScr - и
экран будет очищен. Отсюда, кстати, следует вторая выгода от применения процедур
- "скрытие" ее действий от программы. Написав правильную процедуру, Вы можете, в
общем-то, забыть как и что Вы там делали, и применять ее просто по имени (как ClrScr). Кроме того, основная программа становится проще и "прозрачней", из нее
легче уяснить, что же она делает.
Различие в вызове процедуры и функции.
Процедура вызывается как оператор:
ClrScr;
а функция как операнд в выражении:
y := sin(x) + 23;
I. Описание процедур.
Прежде чем применять процедуру, ее нужно описать. Иными
словами "связать" ее имя и те операторы, которые под этим именем будут
выполняться.
Процедуры (как и функции) описываются перед оператором begin,
открывающим тело основной программы:
program <имя программы>;
<описания различных программных объектов>
<описания процедур и функций>
begin
<тело основной программы>
end.
Синтаксис описания таков:
procedure <имя>;
[ <локальные переменные>; ]
begin
<оператор>; -¬
... ¦ "тело" процедуры
<оператор>; --
end;
<имя> - любой идентификатор.
Тело процедуры - любая последовательность операторов.
В необязательном разделе <локальные переменные> описываются внутрен-ние
переменные и т.п. процедуры - то есть те, которые применяются только
в самой процедуре, а вне ее не нужны.
Пример процедуры, которая обнуляет элементы целого массива A размера n:
procedure Zero;
var i: integer;
begin
for i := 1 to n do A[i] := 0;
end;
Переменная i используется как переменная цикла и вне процедуры не нужна.
Поэтому она объявлена как локальная.
Что плохо в этой процедуре? Она всегда обнуляет только массив A. Если
мне понадобится обнулить массив B - писать другую процедуру? Но
действие-то тоже самое. Не лучше ли при вызове сообщить процедуре, какой
именно массив она должна обнулить? И тоже касается длины массива. Для A
это n. Но длина массива B может храниться в другой переменной!
Иными словами, процедуре часто необходимы параметры - те объекты, с
которыми она должна работать и которые меняются от вызова к вызову.
Массив, который надо обнулить и его длина могут быть параметрами
процедуры обнуления массива.
Но процедуре необходимо имя, с помощью которого она могла бы обращаться
к параметру. И в тоже время обнуляемый массив нельзя назвать ни A, ни B,
ему вообще нельзя дать какое-то фиксированное имя. Ведь процедуру
обнуления можно вызвать с любым массивом! Выход в том, чтобы дать
параметрам формальные имена ("псевдонимы") и процедуре при описании указать, какие переменные не
настоящие, а псевдонимы и используются для замены настоящих. И в теле
процедуры для именования параметров применять формальные
имена-псевдонимы.
Формальное имя (говорят формальный параметр) можно подменить любой
переменной того же типа. Какой именно - указывается при вызове
процедуры.
Так снимается противоречие в именах.
Итак, процедуре необходимо указать список формальных параметров (их
имена и типы):
procedure <имя> [ ( <список формальных параметров> ) ];
[ <локальные переменные>; ]
begin
<тело процедуры>
end;
<Список формальных параметров> имеет следующий вид:
[ var] <список имен>: <тип> [; [ var] <список имен>: <тип>...]
Имена в <списке имен> перечисляются через запятую. Перед <списком имен>
может стоять var, а может и не стоять. Переменные, перед которыми var
стоит, называются формальными параметрами-переменными. Если var нет -
такие переменные называются формальными параметрами-значениями (различие
между ними см.).
<Список формальных параметров> может отсутствовать (как в Zero). Такая
процедура называется процедурой без параметров. В качестве примера
рассмотрим процедуру обнуления. Она должно работать с целым массивом
"вообще". Назовем это массив mas. Такого массива на самом деле
нет, это формальное имя. При каждом вызове будем подразумевать под ним
тот массив, который нам нужен. А чтобы процедура знала, что это
ненастоящий массив, занесем это имя в список формальных параметров.
Также поступим с длиной массива.
type massiv = array [1..10] of integer;
...
procedure Zero2( var mas: massiv; k: integer);
var i: integer;
begin
for i := 1 to k do mas[i] := 0;
end;
Эту процедуру в отличие от предыдущей можно применять для обнуления
любого целого массива.
Отдельное объявление типа массива в разделе type вызвано необходимостью
добиться совпадения типов массива при описании и при вызове процедуры.
Если в описании задать тип массива явно
procedure Zero2( var mas: array [1..10] of integer; k: integer);
этой процедуре не удастся передать ни один массив из-за несоответствия
типов. Этот способ (через type) обязателен для передачи в качестве
параметра любого массива, множества, записи и вообще любого
нестандартного типа.
II. Описание функций.
Синтаксис описания функций очень похож на синтаксис
описания процедур:
function <имя> [(<список формальных параметров>)]:<тип возвр.значения>;
[ <локальные переменные>; ]
begin
<тело функции>
end;
Изменено только ключевое слово на function и добавлен <тип возвращаемого
значения>. Все остальное имеет тот же смысл.
Как мы говорили, функция предназначена для вычисления одного значения,
которое и является ее результатом. Он "возвращается" туда, откуда
функция была вызвана. Чтобы определить, какой тип должен иметь
результат, и указывается <тип возвращаемого значения>. Для функции sin
этот тип, например, real.
Существует также одно требование для <тела функции>. Оно заключается в
том, что на любой ветви выполнения функции должен встретиться хотя бы
один оператор вида
<имя функции> := <выражение>;
<выражение> должно иметь результат типа <тип возвращаемого значения>. С
помощью этого оператора <имя функции> ассоциируется с ее результатом
(иными словами Вы сообщаете, какой же результат имеет функция). Если
этого оператора не будет, значение функции останется неопределено.
Внимание! <имя функции> - это не переменная и употребление его справа от
знака присваивания и т.п. запрещено! Например, нельзя записать
<имя функции> := <имя функции> + 1;
или сравнить <имя функции> с чем-то.
Для примера приведу функцию, вычисляющую сумму элементов целого массива:
type massiv = array [1..10] of integer;
...
function summa ( var mas: massiv; k: integer) : integer;
var s, i: integer;
begin
s := 0;
for i := 1 to k do s := s + mas[i];
summa := s;
end;
Переменная s потребовалась, так как в цикле при суммировании она
встречается справа от знака присваивания, следовательно, имя функции
использовать нельзя.
III. Параметры-значения и
параметры-переменные.
По способу использования в процедуре (функции) параметры
делятся на на две группы: параметры-значения и параметры-переменные.
Когда процедура получает параметр-значение, она создает его временную
копию. Все изменения, которые происходят внутри процедуры с параметром,
происходят только с этой копией. По окончании процедуры копия исчезает,
а вместе с ней и изменения.
Параметрами-значениями должны быть те параметры, значение которых в
процедуре не изменяется, не должно изменяться, либо изменения могут быть
потеряны без вреда для результата работы. Это, так сказать, "пассивные"
параметры, они служат только для передачи процедуре каких-то данных.
При получении процедурой параметра-переменной картина совершенно иная!
Создается ссылка на переданную переменную, или, иначе говоря, ее
синоним. Все изменения, которые происходят с синонимом, изменяют и
переданную переменную. По окончании процедуры ссылка-синоним
уничтожается, но все изменения сохраняются!
Параметрами-переменными должны быть параметры, через которые процедура
передает результаты своей работы в основную программу (в самом деле,
если сделать их параметрами-значениями, передать ничего не удастся).
Кроме того, параметрами-переменными рекомендуется делать крупные
программные объекты типа больших массивов, файлов, множеств и т.п.
Объясняется это тем что для создания копии массива из 10000 целых чисел
необходимо 20000 байт памяти (которой может и не быть), а также
относительно большое время. На создание же ссылки тратятся в 10000
меньшие ресурсы. Зачем попусту расходовать память и время?
В процедуре Zero2 обнуляемый массив передается как параметр-переменная -
потому что мы должны обнулить реальный массив, переданный этой процедуре
(в этом и весь смысл), а не его копию.
Для функций все тоже самое. Однако не рекомендуется без особой
необходимости применять в функциях параметры-переменные. Функция должна
возвращать только свой результат, и ей крайне нежелательно изменять
другие переменные основной программы (такое изменение называется
побочным эффектом функции). Это не запрещено, но правила хорошего тона
требуют не злоупотреблять побочными эффектами.
IV. Глобальные и локальные переменные.
Правило скрытия имен.
Переменные и вообще любые программные объекты,
объявленные в основной программе, называются
глобальными ("всеобщими").
Программные объекты, объявленные в процедуре или функции, называются
локальными ("местными","внутренними").
Глобальные объекты доступны (т.е. ими можно пользоваться) в любом месте
программы, за исключением тех случаев, когда действует правило скрытия
имен (см. ниже).
Локальные объекты доступны только в пределах той процедуры или функции,
в которой они описаны. Вне этой процедуры или функции они не существуют!
В том случае, если в процедуре или функции объявлена локальная
переменная с именем уже существующей глобальной, глобальная переменная
становится недоступной и на все области действия данной локальной
переменной это имя обозначает именно ее. То же касается имен любых
других программных объектов. Это называется правило скрытия имен.
Например:
program prim71;
var i: integer;
...
procedure Down;
var i: integer;
begin
i := 0; { глобальная переменная скрыта, значение }
end; { присваивается локальной переменной }
...
begin
i := 10; { значение присваивается глобальной переменной }
Down;
writeln(i); { будет выведено 10, а не 0 }
end.
Более того, локальная и глобальная переменные с одним именем могут иметь даже разный тип! В примере локальная i могла иметь тип real.
Если в двух разных процедурах есть две одноименные локальные переменные,
то это две совершенно различные переменные, которые также могут иметь различный тип (одна из них может быть массивом, другая -
множеством).
V. Вызов процедур.
Для вызова процедуры необходимо указать имя процедуры и
список параметров для работы. Параметры, передаваемые процедуре для
работы, носят название фактических параметров, поскольку являются
именами реально существующих переменных.
<имя процедуры> [ (<список фактических параметров>) ];
Этот оператор называется оператором вызова процедуры.
Конечно же, фактических параметров должно быть столько же, сколько и
формальных. Они должны располагаться в правильном порядке (первому
формальному параметру соответствует первый фактический, второму
формальному - второй фактический, третьему - третий и т.д.). Тип каждого
фактического параметра должен совпадать с типом соответствующего
формального параметра (если на данном месте
стоит формальный параметр-целый массив, в
списке фактических параметров на этом месте должен стоять целый массив
того же типа).
Фактические параметры перечисляются через запятую.
Для формальных параметров-значений разрешается использовать в качестве
фактического любое выражение, имеющее результат того же типа (в
частности это может быть константа, переменная и т.д.). В качестве
передаваемого значения будет использован результат этого выражения.
Для формальных параметров-переменных соответствующий фактический может
быть только переменной того же типа. Это вызвано тем, что на выражение
ссылку завести нельзя и значение ему не присвоишь. А это вполне может
случиться с формальным параметром-переменной.
Примеры (с процедурой Zero2).
var A, B: massiv;
i, k: integer;
...
i := 2;
k := -3;
...
Zero2(A, 10); { обнулить 10 элементов A }
Zero2(B, i + k + 5); { обнулить 4 элемента B }
Zero2(B, i); { обнулить 2 элемента B }
Zero2(A, abs(k)); { обнулить 3 элемента A }
VI. Вызов функций.
При вызове функций используется та же конструкция, что и
при вызове процедур:
<имя функции> [ (<список фактических параметров>) ]
и для функций справедливы все замечания по поводу правил записи
фактических параметров, сделанные в предыдущем разделе.
Однако если процедура вызывается как самостоятельный оператор, то
функция вызывается только как операнд выражения. Функция возвращает в
точку своего вызова вычисленное ею значение, и по правилам Паскаля это
значение должно быть использовано либо при вычислении выражения, либо
для присваивания его переменной и т.п. Например, нельзя записать
sin(x);
так как функция sin вернет свой результат (т.е. синус x) туда, откуда
она была вызвана, и получится бессмысленный оператор вроде
0.45872е00;
(если принять, что результат будет 0.45872е00). Правильной будет запись
y := sin(x);
после того как функция вернет свой результат, получится
y := 0.45872е00;
а это обыкновенное присваивание значения переменной y.
На самом деле все происходит совершенно иначе, но смысл именно такой:
при выполнении программы функция заменяется своим значением, и в том
месте программы, где Вы употребили функцию, должно быть разрешено
использовать значение этого типа.
Итак, вызов функции можно использовать только в тех местах программы,
где разрешено использование выражений того же типа, что и результат
функции.
Из этого следует, что функцию нельзя вызывать как отдельный оператор,
вызывать слева от знака присваивания или использовать в качестве
переменной цикла for
.Зато ее можно вызывать справа от знака присваивания, как фактический
параметр-значение процедуры или функции, как начальное или конечное
значение того же цикла for и т.п. Не надо только забывать, что во всех
случаях производится контроль типов и функция должна возвращать
результат разрешенного в данном месте типа.
Примеры вызова функции summa.
var A, B: massiv;
s1, s2, i, k: integer;
...
k := 10;
i := 3;
s1 := summa(A,10);
s2 := s1 - summa(B, k-i) div 2;
writeln('Сумма 3х элементов A равна ', summa(A, i));
for i := 1 to summa(B,5) do ...
ПОЛЕЗНЫЕ СОВЕТЫ.
1. Обращайте внимание на имена локальных переменных в
процедуре или функции. Если они случайно совпадут с именами глобальных
объектов, то эти глобальные объекты окажутся
недоступными в данной процедуре (функции).
2. Не включайте в тело процедуры (функции) операторы ввода-вывода без
действительной необходимости.
3. Проверьте, через какие параметры в процедуре Вы передаете результат и
не забудьте сделать их параметрами-переменными.
4. Нельзя вызывать функцию как отдельный оператор.
5. Не рекомендуется передавать процедуре (функции) данные через
глобальные переменные.
6. Не рекомендуется применять при описании функций параметры-переменные. |