6.9.1. Параметры. Глобальные и локальные описания

Поскольку процедуры и функции должны обладать определенной независимостью в смысле использования переменных (а также типов и констант), при их введении в программу возникает разделение данных и их типов на глобальные и локальные. Глобальные константы, типы, переменные — это те, которые объявлены в программе вне процедур или функций. Наоборот, локальные — это константы, типы и переменные, существующие только внутри процедур или функций, и объявленные либо в списке параметров (только переменные), либо в разделах CONST, TYPE, VAR внутри процедуры или функции.

- 108 -

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

PROGRAM Main;

| VAR

| Xmain, Ymain : LongInt; {глобальные переменные}

| Res : Real;

| PROCEDURE Proc1( a,b : Word; VAR Result : Real );

| VAR

| Res : Real; { локальная Res, закрывающая глобальную }

| BEGIN

| Res := a*a + b*b; { локальные действия }

| Result:= Xmain+Ymain*Res; {работают глобальные значения}

| Xmain := Xmain+1; { модифицируется глобальное значение}

| END;

TYPE

CONST Другие глобальные объявления, уже

VAR недоступные из процедуры Proc1;

BEGIN

Основной блок, в котором может вызываться Proc1

END.

Рис. 6.6

При совпадении имен локальной и глобальной переменных (типов, констант) сильнее оказывается локальное имя, и именно оно используется внутри подпрограммы. Так, существует неписанное правило: если подпрограмма содержит в себе циклы FOR, то параметры циклов должны быть описаны как локальные переменные. Это предупредит неразбериху при циклическом вызове процедур.

Мы уже отмечали, что параметры, описываемые в заголовке процедур и функций, по сути, являются локальными переменными. Но кроме того, они обеспечивают обмен значениями между вызывающими и вызываемыми частями программы (т.е. теми же процедурами или функциями). Описываемые в заголовке объявления подпрограммы параметры называются формальными, а те, которые подставляются на их место при вызове, — фактическими, ибо они при выполнении как бы замещают все вхождения в подпрограмму своих формальных «двойников».

- 109 -

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

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

Рассмотрим пример процедуры (рис. 6.7), принимающей любое число и возвращающей его квадрат. Если значение квадрата числа превышает значение 100, то оно считается равным 100. При этом должен устанавливаться глобальный «флаг».

На рис. 6.7 отмечены оба способа обмена данными с процедурой: непосредственной модификацией глобальных переменных и передачей переменной через VAR-параметр. Обратите внимание на использование локальной переменной X. Подобные приемы иногда позволяют не вводить лишних локальных переменных.

- 110 -

| VAR

| GlobalFlag : Boolean; {глобальный флаг}

| PROCEDURE GetSQR( X : Real; VAR Sq : Real );

| { процедура не имеет локальных переменных, кроме X }

| CONST

| SQRMAX =100; { локальная простая константа }

| BEGIN { начало тела процедуры }

| { В X запишется квадрат его последнего значения: }

| X := X * X;

| { Результат сравнения запишется в глобальный флаг: }

| GlobalFlag := ( X > SQRMAX );

| if GlobalFlag then

| X:=SQRMAX; { ограничение X }

| Sq := X { возвращение значения }

| END; { конец тела процедуры }

| VAR

| SqGlobal : Real;

| BEGIN { основной (вызывающий) блок }

| GetSQR ( 5, SqGlobal );

| WriteLn( SqGlobal, ' Флаг: ', GlobalFlag )

| END.

Рис. 6.7

Оставим ненадолго процедуры и рассмотрим функции. Идентификатор функции возвращает после вызова скалярное значение заданного типа. Для присвоения функции значения ее имя должно хотя бы однажды появиться в левой части оператора присваивания в теле самой функции. Вызов функции производится уже не обособленно, а в том месте, где необходимо значение функции (в выражениях, вызовах других подпрограмм и т.п.). Например, процедуру GetSQR на рис. 6.7 можно переписать в виде функции (рис. 6.8).

| VAR GlobalFlag : Boolean; { глобальный флаг }

| FUNCTION GetSQR( X : Real ) : Real;

| CONST SQRMAX =100;

| BEGIN

| X := X*X;

| GlobalFlag:=(X>SQRMAX);

| if GlobalFlag then X:=SQRMAX;

| GetSQR := X { возвращение значения }

| END;

Рис. 6.8

- 111 -

| BEGIN { основной (вызывающий) блок }

| WriteLn( GetSQR( 5 ), ' Флаг: ', GlobalFlag )

| END.

Рис. 6.8 (окончание)

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

Функция, как и процедура, может обмениваться значениями с программой и изменять глобальные переменные непосредственно или через параметры-переменные. Обычно, когда функция, кроме выдачи своего значения, меняет какие-либо глобальные значения или производит другие действия, не связанные с вычислениями своего значения, говорят, что она имеет побочный эффект.

Большое значение имеет соблюдение правил соответствия типов при подстановке параметров. Нельзя (да и не получится — компилятор не пропустит!) конструировать типы в описаниях параметров. Можно использовать только уже известные идентификаторы типов. То же самое можно сказать о типе возвращаемого значения функции. И, конечно, число и порядок следования параметров в вызове должен соответствовать описанию процедуры или функции. Кроме того, в Турбо Паскале существует правило, требующее, чтобы параметры, имеющие файловый тип (или сложный тип с файловыми компонентами), были обязательно описаны как VAR-параметры.

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

Область действия меток переходов всегда локальна, и нельзя планировать переходы с помощью оператора Goto из вложенной, например, процедуры, в охватывающую или в основной блок программы, или из программы в процедуру.

- 112 -