Процедуры и функции в языке Паскаль. Сфера действия описаний

Процедуры и функции в языке Паскаль. Сфера действия описаний

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

Структура процедуры или функции очень похожа на структуру главной процедуры, она также содержит раздел описаний и раздел операторов; раздел операторов начинается с 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.