В языке Паскаль (как вы уже поняли из предыдущего материала) существуют понятия процедуры и функции. Процедуры и функции можно определить как замкнутые программные единицы, реализующие некоторый алгоритм. Фактически процедура или функция - это почти программа, почти - потому что она не может выполняться самостоятельно, а всегда вызывается какой-то другой процедурой или функцией. Программы, которые мы до сих пор писали, тоже были процедурами, правда, несколько особенными - главными процедурами. Программа может содержать любое количество процедур и функций, но она всегда содержит одну и только одну главную процедуру, с которой начинается выполнение программы.
Структура процедуры или функции очень похожа на структуру главной процедуры, она также содержит раздел описаний и раздел операторов; раздел операторов начинается с BEGIN и заканчивается END; (но не END. - как у главной процедуры). Единственным новым оператором для вас будет оператор заголовка, с которого начинается всякая процедура и функция. Все процедуры и функции записываются в разделе описаний какой-либо другой процедуры или функции, в том числе и главной процедуры. Оператор заголовка процедуры имеет вид:
PROCEDURE имя ( список параметров ) ;
Здесь имя - имя процедуры (любой идентификатор), список параметров может отсутствовать, но если он есть, записывается в круглых скобках после имени процедуры и имеет вид :
[VAR] имя , ... имя : тип ;
...........................
[VAR] имя , ... имя : тип
Здесь имя - имена параметров, каждый параметр может использоваться внутри процедуры как обычная переменная соответствующего типа. Тип - имя типа, но не описание пользовательского типа; скажем, описание параметра в виде x:1..5 неверно, но, если выше описан соответствующий тип: TYPE MyType=1..5, то параметр можно описать в виде x:MyType. Ключевое слово VAR перед описанием параметров означает в данном случае, что все параметры до ";" или до ")" - параметры-переменные; если же VAR отсутствует, то параметры являются параметрами-значениями. Смысл этих понятий мы рассмотрим несколько позже.
Процедуры вызываются в других процедурах и функциях с помощью уже известного вам оператора вызова:
имя ( список аргументов );
Список аргументов задается в том и только в том случае, когда в заголовке процедуры задан список параметров. Аргументы в списке разделяются запятыми и могут быть любыми выражениями, если соответствующий параметр есть параметр-значение, или только именами переменных, если соответствующий параметр есть параметр-переменная. Количество аргументов всегда должно совпадать с количеством параметров, и тип аргумента должен быть таким же, как тип параметра. При вызове процедуры значение соответствующих аргументов передается параметрам, и таким образом процедура получает информацию из вызывающей процедуры или функции. Запишем программу, использующую процедуру, которая будет аккуратно выводить значение переменной :
PROCEDURE OutVar(x:Real; Name:Char);
BEGIN WRITELN('Переменная ',Name,' равна ',x); END;
VAR a,b,c,d : Real;
BEGIN WRITE('Введите переменные a,b,c,d '); READ(a,b,c,d);
OutVar(a,'a'); OutVar(b,'b'); OutVar(c,'c'); OutVar(d,'d');
END.
Наша процедура OutVar получает из главной процедуры вещественное число x и символ Name, но ничего не передает обратно. Теперь попробуем написать процедуру, которая по заданным значениям x и y вычисляет cos(x)+cos(y) и cos(x)-cos(y) :
PROCEDURE T(x,y:Real; Cplus,Cminus:Real);
BEGIN Cplus:=cos(x)+cos(y); Cminus:=cos(x)-cos(y); END;
VAR p,m:Real;
BEGIN T(1.235,0.645,p,m); WRITELN(p:7:3,m:7:3); END.
Запустим эту программу и - вместо правильного результата 1.129,-0.470 - получим в лучшем случае нули. Дело в том, что через параметры-значения (а Cplus и Cminus описаны в нашей процедуре как параметры-значения!) невозможно передать информацию из процедуры, но лишь в процедуру. Чтобы правильно решить нашу задачу, следует Cplus и Cminus описать в заголовке как параметры-переменные:
PROCEDURE T(x,y:Real; VAR Cplus,Cminus:Real);
BEGIN Cplus:=cos(x)+cos(y); Cminus:=cos(x)-cos(y); END;
Таким образом, входные параметры процедуры могут быть и параметрами -значениями и параметрами-переменными, а выходные параметры - только параметрами-переменными. Для того, чтобы глубже понять это правило, выясним, что же происходит с параметрами и аргументами при вызове процедуры. В момент вызова для каждого параметра-значения в специальной области памяти, называемой стеком (за контроль переполнения стека отвечает описанная выше опция компилятора Stack cheking), создается его копия - переменная соответствующего типа, которой присваивается значение аргумента. В дальнейшем процедура работает с этой копией, и при выходе из процедуры копия уничтожается. Таким образом, никакие изменения параметра-значения не могут быть известны за пределами процедуры. По-другому обрабатываются параметры-переменные: в процедуру передается не значение аргумента, а его адрес, и она работает с аргументом (теперь понятно, почему аргумент, соответствующий параметру-переменной, должен быть только именем переменной: он должен иметь адрес). Так что все изменения параметра на самом деле происходят с аргументом и известны в вызывающей процедуре.
Функция, в отличие от процедуры, всегда вычисляет некоторое значение скалярного типа, которое внутри функции должно быть присвоено имени функции. Заголовок функции имеет вид:
FUNCTION имя ( список параметров ) : тип ;
В остальном функции аналогичны процедурам. Обращение к функции осуществляется с помощью указателя функции:
имя ( список параметров )
Указатель функции может использоваться как и любое другое выражение того же типа, но это не оператор, в отличие от оператора вызова. Запишем пример функции:
FUNCTION Na3(x:LongInt):Boolean;
{ функция проверяет, делится ли x на 3 }
BEGIN Na3:=x MOD 3=0; END;
VAR L:LongInt;
BEGIN WRITE('Введите целое число '); READ(L);
WRITE('Число ',L);
IF NOT Na3(L) THEN WRITE(' не');
WRITELN(' делится на 3 !');
END.
В любой процедуре и функции можно использовать черезвычайно полезную стандартную процедуру Exit без параметров для немедленного выхода в вызывающую процедуру.
Все процедуры и функции Паскаля являются рекурсивными, то есть могут вызывать сами себя, никаких усилий со стороны программиста для этого не требуется. В качестве примера запишем функцию, вычисляющую n!
FUNCTION Factorial(n:Byte):Real;
BEGIN IF n<=1 THEN Factorial:=1
ELSE Factorial:=n*Factorial(n-1);
END;
Но это, конечно, очень плохая функция, гораздо лучше записать этот алгоритм так:
FUNCTION Factorial(n:Byte):Real;
VAR i:Byte; f:Real;
BEGIN f:=1; FOR i:=2 TO n DO f:=f*i;
Factorial:=f;
END;
Итак, мы знаем, что программа может содержать много процедур и функций, и в каждой из них могут быть описаны типы, константы и переменные. Но не все из них могут быть использованы в любом месте программы, каждое описание имеет строго определенную сферу действия. Пусть процедура А находится внутри процедуры В - условимся называть процедуру А внутренней по отношению к В, а процедуру В - объемлющей по отношению к А. Если же ни процедура А не находится внутри В, ни В не находится внутри А, то эти процедуры - внешние по отношению друг к другу. Сфера действия описания любого объекта включает ту процедуру, где он описан (начиная с места описания) и все внутренние процедуры, если там данный идентификатор не описан. В принципе, это дает возможность передавать информацию в процедуры и функции, минуя параметры, то есть пользоваться во внутренней процедуре переменными, описанными в объемлющей процедуре, но такой стиль программирования считается ненадежным. Старайтесь, если это возможно, все переменные, используемые в процедуре, описывать в этой процедуре.
Для чего нужны процедуры и функции, когда и как их следует применять? Многие начинающие программисты избегают процедур и функций, утверждая, что "без них проще". На самом деле обойтись без функций и процедур легко только в самых тривиальных программах. Сколько-нибудь сложная программа, записанная "одним куском", требует при отладке от программиста огромных усилий, которые зачастую все равно пропадают даром. Обязательно используйте в своих программах процедуры и функции! Хорошая программа должна содержать главным образом обращения к процедурам и функциям. Конечно, не существует никаких жестких правил, определяющих, когда использовать функции, а когда нет, но автор этой книжки может предложить несколько нестрогих, но полезных рецептов:
- выделяйте в процедуру (функцию) небольшой логически завершенный фрагмент алгоритма;
- не смешивайте в одной процедуре (функции) ввод-вывод данных и вычислительные алгоритмы;
- называйте свои процедуры (функции) мнемоническими именами;
- если алгоритм, который вы решили выделить в процедуру (функцию), все еще слишком сложен, оформите фрагмент этого алгоритма в другой процедуре (функции);
- если алгоритм должен вычислить одно скалярное значение, пусть это будет функция, а не процедура;
- если в вашей программе встречаются многократно вложенные циклы или "многоэтажные" условные операторы, это верный признак, что вам нужны процедуры (функции);
- если текст вашей программы не умещается на одном экране - подумайте о процедурах;
- используйте в процедурах и функциях процедуру Exit.