Процедуры и функции позволяют включать в основной программ-
ный блок дополнительные блоки. Каждое описание процедуры или
функции содержит заголовок, за которым следует программный блок.
Процедура активизируется с помощью оператора процедуры. Функция
активизируется при вычислении выражения, содержащего вызов функ-
ции, и возвращаемое функцией значение подставляется в это выраже-
ние.
Примечание: Определение блока вы можете найти в Главе
8 "Блоки, локальность и область действия".
В данной главе обсуждаются различные способы описания проце-
дуры или функции и их параметры.
Описания процедур
-----------------------------------------------------------------
Описание процедуры позволяет связать идентификатор с проце-
дурным блоком. Процедуру можно затем активизировать с помощью
оператора процедуры.
---------- ---- ------------- ----
описание --->¦заголовок+-->¦ ; +-->¦ тело +-->¦ ; +-->
процедуры ¦процедуры¦ L---- ¦подпрограммы¦ L----
L---------- L-------------
---------- --------------
заголовок -->¦procedure+-T>¦идентификатор+--
процедуры L---------- ¦ L-------------- ^+------------------>
¦ -------------- ¦¦ ----------- ^
¦ ¦ уточненный ¦ ¦¦ ¦ список ¦ ¦
L>¦идентификатор+--L->¦формальных+--
¦ метода ¦ ¦параметров¦
L-------------- L-----------
-------
блок ---T------------------------------T-->¦модуль+-------->
подпрограммы¦ ---------- ---- ^ ¦ L------- ^
+-->¦ near +----->¦ ; +---- ¦ -------- ¦
¦ L---------- ^ L---- ¦-->¦forward+---+
¦ ---------- ¦ ¦ L-------- ¦
+-->¦ far +--+ ¦ ---------- ¦
¦ L---------- ¦ ¦-->¦директива+-+
¦ ---------- ¦ ¦ ¦ external¦ ¦
+-->¦ export +--+ ¦ L---------- ¦
¦ L---------- ¦ ¦ --------- ¦
¦ ---------- ¦ L-->¦блок asm+--+
+-->¦interrupt+--- L--------- ¦
¦ L---------- ---------- ¦
L--------------------------------->¦директива+--
¦ inline ¦
L----------
Заголовки процедур именуют идентификаторы процедур и задают
формальные параметры (если они имеются).
Примечание: Синтаксис списка формальных параметров по-
казан далее в этой главе в разделе "Параметры".
Процедура активизируется с помощью оператора процедуры, в
котором содержатся имя процедуры и необходимые параметры. Опера-
торы, которые должны выполняться при запуске процедуры, содержат-
ся в операторной части модуля процедуры. Если в содержащемся в
процедуре операторе внутри модуля процедуры используется иденти-
фикатор процедуры, то процедура будет выполняться рекурсивно (бу-
дет при выполнении обращаться сама к себе).
Приведем пример описания процедуры:
procedure NumString(N: integer; var S: string);
var
V: integer;
begin
V := Abs(N);
S := '';
repeat
S := Chr(N mod 10 + Ord('0')) + S;
N := N div 10;
until N = 0;
if N < 0 then S := '-' + S;
end;
Описания near и far
Borland Pascal поддерживает две модели вызова процедур -
ближнюю (near) и дальнюю (far). С точки зрения объема программы и
скорости выполнения ближняя модель вызова более эффективна, но с
ней связаны ограничения: процедуры типа near могут вызываться
только в том модуле, где они описаны. Процедуры же с дальним ти-
пом вызова можно вызывать из любого модуля, но они несколько ме-
нее эффективны.
Примечание: О вызовах ближнего и дальнего типа расска-
зывается в Главе 22 "Вопросы управления".
На основе описания процедуры компилятор будет автоматически
выбирать правильную модель вызова. Для процедур, описанных в ин-
терфейсной части модуля (interface), используется дальняя модель
вызова - их можно вызывать из других модулей. Процедуры, описан-
ные в секции реализации модуля (implementation), имеют ближний
тип вызова. Вызываться они могут только из программ данного моду-
ля.
Для некоторых специальных целей может потребоваться исполь-
зовать модель с дальним типом вызова. Например, в оверлейных за-
дачах обычно требуется, чтобы все процедуры и функции имели даль-
ний тип вызова. Аналогично, если процедура или функция
присваивается процедурной переменной, то она также должна исполь-
зовать дальний тип вызова. Чтобы переопределить автоматический
выбор модели вызова компилятором, можно использовать директиву
компилятора {$F+}. Процедуры и функции, компилируемые в состоянии
{$F+}, всегда будут иметь дальний тип вызова (far), а в состоянии
{$F-} компилятор автоматически выбирает корректную модель. По
умолчанию используется директива {$F-}.
Чтобы задать конкретную модель вызова, в описании процедуры
перед ее блоком можно указать директиву near или far. При наличии
такой директивы она переопределяет директиву $F компилятора и ав-
томатический выбор модели вызова.
Описания export
Описание export делает процедуру или функцию экспортируемой,
вынуждая компилятор использовать для нее дальний тип вызова и ге-
нерировать специальный код входы и выхода из процедуры.
Процедуры и функции должны быть экспортируемыми в следующих
случаях:
* Процедуры и функции экспортируются DLL (динамически компо-
нуемой библиотекой).
* Процедуры и функции системного вызова в программе Windows.
О том, как экспортировать процедуры и функции в DLL, расска-
зывается в Главе 11 "Динамически компонуемые библиотеки". Хотя
процедура и функция компилируется с директивой export, фактичес-
кий экспорт процедуры или функции не происходит, пока подпрограм-
ма не перечисляется в операторе exports библиотеки.
Процедуры и функции системного вызова - это те процедуры и
функции вашей прикладной программы, которые вызываются самой
Windows, а не вашей прикладной программой. Подпрограммы системно-
го вызова должны компилироваться с директивой export, но в опера-
торе exports их перечислять не нужно. Приведем некоторые примеры
процедур и функций системного вызова:
* процедуры Windows;
* диалоговые процедуры;
* процедуры системного вызова для перечисления;
* процедуры уведомления об обращении к памяти;
* специализированные процедуры Windows (фильтры).
Borland Pascal автоматически генерирует для процедур и функ-
ций, экспортируемых программой Windows, эффективные системные вы-
зовы. Эффективные вызовы ослабляют необходимость использования
при создании подпрограмм системного вызова подпрограмм API
Windows MakeProcInstance и FreeProcInstance.
Примечание: См. раздел "Код входа и выхода" в Главе 22.
Описания interrupt
В описании процедуры перед блоком операторов может указыва-
ется директива interrupt. Процедура в этом случае рассматривает-
ся, как процедура прерывания. Отметим пока, что процедура
interrupt не может вызываться из операторов процедуры, и что каж-
дая процедура interrupt должна определять список параметров, нап-
ример, следующим образом:
procedure MyInt(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS,
ES, BP: Word);
interrupt;
Примечание: Не используйте директиву interrupt при
разработке программ для Windows - это приведет к сбою.
Список параметров не обязательно должен совпадать с указан-
ным синтаксисом - он может быть короче и использовать другие име-
на, но регистры должны передаваться в указанном порядке.
Описание forward
Описание процедуры, содержащее вместо блока операторов ди-
рективу forward, называется опережающим описанием. В каком-либо
месте после этого описания с помощью определяющего описания про-
цедура должна определяться. Определяющее описание - это описание,
в котором используется тот же идентификатор процедуры, но опущен
список формальных параметров и в которое включен блок операторов.
Описание forward и определяющее описание должны присутствовать в
одной и той же части описания процедуры и функции. Между ними мо-
гут описываться другие процедуры и функции, которые могут обра-
щаться к процедуре с опережающим описанием. Таким образом возмож-
на взаимная рекурсия.
Опережающее описание и определяющее описание представляют
собой полное описание процедуры. Процедура считается описанной с
помощью опережающего описания.
Примечание: В интерфейсной части модуля описания
forward не допускаются.
Приведем следующий пример опережающего описания:
procedure Walter(m,n : integer); forward;
procedure Clara(x,y : real);
begin
.
.
.
end;
procedure Walter;
begin
.
.
Clara(8.3, 2.4);
.
.
end;
Определяющее описание процедуры может быть внешним описани-
ем. Однако, оно не может быть внутренним описанием или другим
опережающим описанием. Определяющее описание также не может со-
держать директиву interrupt, описания assembler, near, far,
export, inline или другое описание forward.
Описания external
Описания external позволяют связывать отдельно скомпилиро-
ванные процедуры и функции, написанные на языке ассемблера. Опи-
сания external позволяют также импортировать процедуры и функции
из DLL.
Примечание: Более детальное описания компоновки с
программой на языке ассемблера содержится в Главе 25.
директива external
¦ -----------
L->¦ external +T----------------------------------------------->
L-----------¦ -------------------- ^
L>¦строковая константа+T------------------------
L--------------------¦ ------- ----------^
+>¦ name +->¦строковая++
¦ L------- ¦константদ
¦ L----------¦
¦ -------- ----------¦
L>¦ index +>¦ целая +-
L-------- ¦константа¦
L----------
Директива external, состоящая только из зарезервированного
слова external, используется в сочетании с директивами {$L
имя_файла} для компоновки с процедурами и функциями, реализован-
ными в файлах .OBJ.
Приведем следующие примеры описаний внешних процедур:
procedure MoveWord(var source,dest; count: longint);
external;
procedure MoveLong(var source,dest; count: longint);
external;
procedure FillWord(var dest,data: integer; count: longint);
external;
procedure FillLong(var dest,data: integer; count: longint);
external;
{$L BLOCK.OBJ}
Внешними процедурами следует пользоваться, когда вы хотите
объединить большое количество объектных модулей. Если ваши прог-
раммы имеют небольшой объем, лучше вместо этого использовать
внутренние процедуры.
Директивы external, специфицирующие имя динамически компону-
емой библиотеки (и, возможно, импортируемое имя или порядковый
номер импорта), используются для импорта процедур и функций из
динамически компонуемых библиотек. Например, следующая директива
external импортирует из DLL с именем KERNEL (ядро Windows) функ-
цию с именем GlobalAlloc:
function GlobalAlloc(Flags: Word; Bytes: Longint): THandle;
far; external 'KERNEL' index 15;
В импортируемой процедуре или функции директива external за-
нимает место описания и операторной части. В импортируемых проце-
дурах или функциях должен использоваться дальний тип вызова, за-
даваемый с помощью директивы far в описании процедуры или дирек-
тивы компилятора {$F+}. В остальном импортируемые процедуры и
функции аналогичны обычным процедурам и функциям.
Примечание: Подробнее об импорте функций из DLL расс-
казывается в Главе 11.
Описания assembler
Описания assembler позволяют вам написать всю процедуру или
функцию на ассемблере.
Примечание: Более подробно о процедурах и функциях на
Ассемблере рассказывается в Главе 24 "Встроенный ассемблер".
---------- ---- ----------- -------------
блок asm ->¦assembler+-->¦ ; +-->¦ раздел +-->¦asm оператор+->
L---------- L---- ¦ описания ¦ L-------------
L-----------
Описания inline
Директивы inline позволяют записывать вместо блока операто-
ров инструкции в машинном коде. При вызове обычной процедуры ком-
пилятор создает код, в котором параметры процедуры помещаются в
стек, а затем для вызова процедуры генерируется инструкция CАLL.
------------------
директива inline -->¦ оператор inline +---------->
L------------------
Когда вы вызываете подставляемую процедуру (inline), компи-
лятор генерирует код с помощью директивы inline, а не с помощью
инструкции CALL. Таким образом, поставляемая процедура "расширя-
ется" при каждом обращении к ней, аналогично макроинструкции на
языке ассемблера. Приведем два небольших примера подставляемых
процедур:
procedure DisableInterrupts: inline($FA); { CLI }
procedure EnableInterrupts; inline($FB); { STI }
Примечание: Синтаксические диаграммы оператора inline
описаны подробно в Главе 25.
Описания функций
Описание функции определяет часть программы, в которой вы-
числяются и возвращается значение.
---------- ---- -------- ----
описание --->¦заголовок+-->¦ ; +-->¦ тело +-->¦ ; +-->
функции ¦ функции ¦ L---- ¦функции¦ L----
L---------- L--------
--------- --------------
заголовок --->¦function+T>¦идентификатор+--T-------------------
функции L---------¦ L--------------^ ¦ ----------- ^ ¦
¦ --------------¦ ¦ ¦список ¦ ¦ ¦
L>¦ уточненный +- L->¦формальных+--- ¦
¦идентификатор¦ ¦параметров¦ ¦
¦ метода ¦ L----------- ¦
L-------------------------------------
¦ ---- ---------
L->¦ : +-->¦тип ре- +-->
L---- ¦зультата¦
L---------
--------------
тип результата --T-->¦идентификатор+--------->
¦ ¦ типа ¦ ^
¦ L-------------- ¦
¦ ------- ¦
L----->¦string+----------
L-------
Примечание: Функция не может возвращать процедурный
тип или структурный тип.
В заголовке функции определяется идентификатор функции, фор-
мальные параметры (если они имеются) и тип результата функции.
Функция активизируется при вызове функции. При вызове функ-
ции указывается идентификатор функции и какие-либо параметры, не-
обходимые для вычисления функции. Вызов функции может включаться
в выражения в качестве операнда. Когда выражение вычисляется,
функция выполняется и значением операнда становится значение,
возвращаемое функцией.
В операторной части блока функции задаются операторы, кото-
рые должны выполняться при активизации функции. В модуле должен
содержаться по крайней мере один оператор присваивания, в котором
идентификатору функции присваивается значение. Результатом функ-
ции является последнее присвоенное значение. Если такой оператор
присваивания отсутствует или он не был выполнен, то значение,
возвращаемое функцией, не определено.
Если идентификатор функции используется при вызове функции
внутри модуля-функции, то функция выполняется рекурсивно.
Приведем далее примеры описаний функции:
function Max(a: Vector; n: integer): extended;
var
x: extended;
i: integer;
begin
x := a(1);
for i := 2 to n do if x < a[i] then x := a[i];
Max := x;
end;
function Power(x: extended; y: integer): extended;
var
z: extended;
i: integer;
begin
z := 1.0; i := y;
while i > 0 do
begin
if Odd(i) then z := z*x;
x := Sqr(x);
end;
Power := z;
end;
Аналогично процедурам функции могут описываться, как с ближ-
ним типом вызова (near), с дальним типом вызова (far), опережаю-
щие (forward), внешние (external), ассемблерные (assembler) или
подставляемые (inline). Однако функции прерываний (interrupt) не
допускаются.
Описания методов
Описание метода внутри объектного типа соответствует опере-
жающему описанию (forward) этого метода. Таким образом, метод
должен быть реализован где-нибудь после описания объектного типа
и внутри той же самой области действия метода путем определяющего
описания.
Для процедурных и функциональных методов определяющее описа-
ния имеет форму обычного описания процедуры или функции, за тем
исключением, что в этом случае идентификатор процедуры или функ-
ции рассматривается как идентификатор метода.
Для методов конструкторов и деструкторов определяющее описа-
ний принимает форму описания процедурного метода, за тем исключе-
нием, что зарезервированное слово procedure заменяется на заре-
зервированное слово constructor или destructor. Определяющее опи-
сание метода может повторять (но не обязательно) список формаль-
ных параметров заголовка метода в объектном типе. В этом случае
заголовок метода должен в точности повторять заголовок в объект-
ном типе в порядке, типах и именах параметров и в типе возвращае-
мого функцией результата, если метод является функцией.
В определяющем описании метода всегда присутствует неявный
параметр с идентификатором Self, соответствующий формальному па-
раметру-переменной, обладающему объектным типом. Внутри блока ме-
тода Self представляет экземпляр, компонент метода которого был
указан для активизации метода. Таким образом, любые изменения
значений полей Self отражаются на экземпляре.
Область действия идентификатора компонента объектного типа
распространяется на блоки процедур, функций, конструкторов и
деструктора, которые реализуют методы данного объектного типа.
Эффект получается тот же, как если бы в начало блока метода был
вставлен оператор with в следующей форме:
with Self do
begin
...
end;
Исходя из этих соображений, написание идентификаторов компо-
нентов, формальных параметров метода, Self и любого идентификато-
ра, введенного в исполняемую часть метода, должно быть уникаль-
ным.
Ниже приводятся несколько примеров реализаций методов:
procedure Rect.Intersect(var R: Rect);
begin
if A.X < R.A.X then A.X := R.A.X;
if A.X < R.A.Y then A.Y := R.A.Y;
if B.X > R.B.X then B.X := R.B.X;
if B.Y < R.B.Y then B.Y := R.B.Y;
if (A.X >= B.X) or (A.Y >= B.Y) then Init (0, 0, 0, 0);
end;
procedure Field.Display;
begin
GotoXY(X, Y);
Write(Name^, ' ', GetStr);
end;
function NumField.PutStr(S: string): boolean;
var
E: integer;
begin
Val(S, Value, E);
PutStr := (E = 0) and (Value >= Min) and (Value <= Max);
end;
Конструкторы и деструкторы
Конструкторы и деструкторы являются специализированными фор-
мами методов. Используемые в связи с расширенным синтаксисом
стандартных процедур New и Dispose, конструкторы и деструкторы
обладают способностью размещения и удаления динамических объек-
тов. Кроме того, конструкторы имеют возможность выполнить требуе-
мую инициализацию объектов, содержащих виртуальные методы. Как и
все другие методы, конструкторы и деструкторы могут наследовать-
ся, а объекты могут содержать любое число конструкторов и дест-
рукторов.
Конструкторы используются для инициализации вновь созданных
объектов. Обычно инициализация основывается на значениях, переда-
ваемых конструктору в качестве параметров. Конструктор не может
быть виртуальным, так как механизм диспетчеризации виртуального
метода зависит от конструктора, который первым совершил инициали-
зацию объекта.
------------- ---- ------------- ----
описание --->¦ заголовок +-->¦ ; +-->¦ блок +-->¦ ; +->
конструктора ¦конструктора¦ L---- ¦подпрограммы¦ L----
L------------- L-------------
------------ --------------
заголовок ---->¦constructor+T>¦идентификатор+-T---------------->
конструктора L------------¦ L--------------^¦ ----------- ^
¦ --------------¦¦ ¦ список ¦ ¦
L>¦ уточненный +-L->¦формальных+--
¦идентификатор¦ ¦параметров¦
¦ метода ¦ L-----------
L--------------
Приведем несколько примеров конструкторов:
constructor Field.Copy(var F: Field);
begin
Self := F;
end;
constructor Field.Init(FX, FY, FLen: integer; FName: string);
begin
X := FX;
Y := FY;
GetMem(Name, Length (FName) + 1);
Name^ := FName;
end;
constructor TStrField.Init(FX, FY, FLen: integer; FName:
string);
begin
inherited Init(FX, FY, FLen, FName);
Field.Init(FX, FY, FLen, FName);
GetMem(Value, Len);
Value^ := '';
end;
Главным действием конструктора порожденного (дочернего) ти-
па, такого как указанный выше TStrField.Init, почти всегда явля-
ется вызов соответствующего конструктора его непосредственного
родителя для инициализации наследуемых полей объекта. После вы-
полнения этой процедуры, конструктор инициализирует поля объекта,
которые принадлежат только порожденному типу.
Деструкторы ("сборщики мусора") являются противоположностями
конструкторов и используются для очистки объектов после их ис-
пользования. Обычно очистка состоит из удаления всех полей-указа-
телей в объекте.
Примечание: Деструктор может быть виртуальным и часто
является таковым. Деструктор редко имеет параметры.
Приведем несколько примеров деструкторов:
destructor Field.Done;
begin
FreeMem(Name, Length (Name^) + 1);
end;
destructor StrField.Done;
begin
FreeMem(Value, Len);
Field.Done;
end;
Деструктор дочернего типа, такой как указанный выше
TStrField.Done, обычно сначала удаляет введенные в порожденном
типе поля указателей, а затем в качестве последнего действия вы-
зывает соответствующий сборщик деструктор непосредственного роди-
теля для удаления унаследованных полей-указателей объекта.
Восстановление ошибок конструктора
Borland Pascal позволяет вам с помощью переменной HeapError
модуля System (см. Главу 21) установить функцию обработки ошибки
динамически распределяемой области. Эта функциональная возмож-
ность влияет на способ работы конструкторов объектного типа.
По умолчанию, когда для динамического экземпляра объекта не
хватает памяти, вызов конструктора, использующий расширенный син-
таксис стандартной процедуры New, генерирует ошибку этапа выпол-
нения 203. Если вы установили функцию обработки ошибки динамичес-
ки распределяемой области, которая вместо стандартного результата
функции 0 возвращает 1, когда выполнить запрос невозможно, вызов
конструктора через New возвращает nil (вместо прерывания програм-
мы).
Код, выполняющий распределение памяти и инициализацию поля
таблицы виртуальных методов (VMT) динамического экземпляра объек-
та является частью последовательности вызова конструктора. Когда
управление передается на оператор begin операторной части конс-
труктора, память для экземпляра уже выделена, и он инициализиро-
ван. Если выделения памяти завершается неудачно, и если функция
обработки ошибки динамически распределяемой области памяти возв-
ращает 1, конструктор пропускает выполнение операторной части и
возвращает значение nil. Таким образом, указатель, заданный в вы-
полняемом конструктором вызове New, устанавливается в nil.
Когда управление передается на оператор begin операторной
части конструктора, для экземпляра объектного типа обеспечивается
успешное выполнение памяти и инициализация. Сам конструктор может
попытаться распределить динамические переменные для инициализации
полей-указателей в экземпляре, однако, такое распределение может
завершиться неудачно. Если это происходит, правильно построенный
конструктор должен отменять все успешные распределения и, нако-
нец, освобождать выделенную для экземпляра объекта память, так
что результатом может стать указатель nil. Для выполнения такой
"отмены" Borland Pascal реализует стандартную процедуру Fail, ко-
торая не требует параметров и может вызываться только из конс-
труктора. Вызов Fail приводит к тому, что конструктор будет осво-
бождать выделенную для динамического экземпляра память, которая
была выделена перед входом в конструктор, и для указания неудачи
возвращает указатель nil.
Когда память для динамических экземпляров выделяется с по-
мощью расширенного синтаксиса New, результирующее значение nil
в заданном указателе-переменной указывает, что операция заверши-
лась неудачно. К сожалению, не существует такого указателя-пере-
менной, которую можно проверить после построения статического эк-
земпляра или при вызове наследуемого конструктора. Вместо этого
Borland Pascal позволяет использовать конструктор в виде булевс-
кой функции в выражении: возвращаемое значение True указывает на
успешное выполнение, а значение False - не неуспешное выполнение
из-за вызова в конструкторе Fail.
На диске вы можете найти две программы - NORECVER.PAS и
RECOVER.PAS. Оба программы реализуют два простых объектных типа,
содержащих указатели. Первая программа не содержит восстановления
ошибок конструктора.
Программа RECOVER.PAS демонстрирует, как можно переписать
исходный код для реализации восстановления ошибки. Заметим, что
для отмены успешного выделения памяти перед вызовом Fail для ито-
гового неуспешного выполнения используются соответствующие дест-
рукторы в Base.Init и Derived.Init. Заметим также, что в
Derived.Init вызов Base.Init содержится внутри выражения, так что
можно проверить успешность выполнения наследуемого конструктора.
Параметры
В описании процедуры или функции задается список формальных
параметров. Каждый параметр, описанный в списке формальных пара-
метров, является локальным по отношению к описываемой процедуре
или функции и в модуле, связанным с данной процедурой или функци-
ей на него можно ссылаться по его идентификатору.
---- ----------- ----
список формальных --->¦ ( +----->¦ описание +--T-->¦ ) +-->
параметров L---- ^ ¦параметра ¦ ¦ L----
¦ L----------- ¦
¦ ---- ¦
L------+ ; ¦<------
L----
--------------
описание --T------------>¦список иден- +T--------------------->
параметра ¦ ---- ^ ¦тификаторов ¦¦ ^
+->¦var+----+ L--------------¦ ---- -------- ¦
¦ L---- ¦ L>¦ : +->¦тип па-+--
¦ ------ ¦ L---- ¦раметра¦
L->¦const+--- L--------
L------
Существует три типа параметров: значение, переменная и нети-
пизированная переменная. Они характеризуются следующим:
1. Группа параметров без предшествующего ключевого слова
является списком параметров-значений.
2. Группа параметров, перед которыми следует ключевое слово
const и за которыми следует тип, является списком пара-
метров-констант.
3. Группа параметров, перед которыми стоит ключевое слово
var и за которыми следует тип, является списком нетипи-
зированных параметров-переменных.
4. Группа параметров, перед которыми стоит ключевое слово
var или const за которыми не следует тип, является спис-
ком нетипизированных параметров-переменных.
Параметры строкового типа и массивы могут быть открытыми па-
раметрами. Параметры-переменные, описанные с помощью идентифика-
тора OpenString или с использованием ключевого слова string в
состоянии {$P+}, являются открытыми строковыми параметрами. Зна-
чение, константа или параметр-переменная, описанные с помощью
синтаксиса array of T, являются открытым параметром-массивом.
Примечание: Подробнее об открытых параметрах рассказы-
вается ниже.
Параметры-значения
Формальный параметр-значение обрабатывается, как локальная
по отношению к процедуре или функции переменная, за исключением
того, что он получает свое начальное значение из соответствующего
фактического параметра при активизации процедуры или функции. Из-
менения, которые претерпевает формальный параметр-значение, не
влияют на значение фактического параметра.
Соответствующее фактическое значение параметра-значения
должно быть выражением и его значение не должно иметь файловый
тип или какой-либо структурный тип, содержащий в себе файловый
тип.
Фактический параметр должен иметь тип, совместимый по прис-
ваиванию с типом формального параметра-значения. Если параметр
имеет строковый тип, то формальный параметр будет иметь атрибут
размера, равный 255.
Параметры-константы
Формальные параметры-константы работают аналогично локальной
переменной, доступной только по чтению, которая получает свое
значение при активизации процедуры или функции от соответствующе-
го фактического параметра. Присваивания формальному парамет-
ру-константе не допускаются. Формальный параметр-константа также
не может передаваться в качестве фактического параметра другой
процедуре или функции.
Параметр-константа, соответствующий фактическому параметру в
операторе процедуры или функции, должен подчиняться тем же прави-
лам, что и фактическое значение параметра.
В тех случаях, когда формальный параметр не изменяет при вы-
полнении процедуры или функции своего значения, вместо парамет-
ра-значения следует использовать параметр-константу. Парамет-
ры-константы позволяют при реализации процедуры или функции защи-
титься от случайных присваиваний формальному параметру. Кроме то-
го, для параметров структурного и строкового типа компилятор при
использовании вместо параметров-значений параметров-констант мо-
жет генерировать более эффективный код.
Параметры-переменные
Параметр-переменная используется, когда значение должно пе-
редаваться из процедуры или функции вызывающей программе. Соот-
ветствующий фактический параметр в операторе вызова процедуры или
функции должен быть ссылкой на переменную. При активизации проце-
дуры или функции формальный параметр-переменная замещается факти-
ческой переменной, любые изменения в значении формального пара-
метра-переменной отражаются на фактическом параметре.
Внутри процедуры или функции любая ссылка на формальный па-
раметр-переменную приводит к доступу к самому фактическому пара-
метру. Тип фактического параметра должен совпадать с типом фор-
мального параметра-переменной (вы можете обойти это ограничение с
помощью нетипизированного параметра-переменной).
Примечание: Файловый тип может передаваться только,
как параметр-переменная.
Директива компилятора $P управляет смыслом параметра-пере-
менной, описываемого с ключевым словом string. В состоянии по
умолчанию ({$P-}) string соответствует строковому типу с атрибу-
том размера 255. В состоянии {$P+} string указывает, что параметр
является открытым строковым параметром (см. ниже).
При ссылке на фактический параметр-переменную, связанную с
индексированием массива или получением указателя на объект, эти
действия выполняются перед активизацией процедуры или функции.
Правила совместимости по присваиванию для объектного типа
применяются также к параметрам-переменным объектного типа. Для
формального параметра типа T1 фактический параметр должен быть
типа T2, если T2 находится в домене T1. Например, с учетом опи-
саний Главы 4, методу TField.Copy может передаваться экземпляр
TField, TStrField, TNumField, TZipField или любой другой экземп-
ляр потомка TField.
Нетипизированные параметры
Когда формальный параметр является нетипизированным парамет-
ром-переменной, то соответствующий фактический параметр может
представлять собой любую ссылку на переменную или константу, не-
зависимо от ее типа. Нетипизированный параметр, описанный с клю-
чевым словом var, может модифицироваться, а нетипизированный па-
раметр, описанный с ключевым словом const, доступен только по
чтению.
В процедуре или функции у нетипизированного параметра-пере-
менной тип отсутствует, то есть он несовместим с переменными всех
типов, пока ему не будет присвоен определенный тип с помощью
присваивания типа переменной.
Приведем пример нетипизированных параметров-переменных:
function Equal(var source,dest; size: word): boolean;
type
Bytes = array[0..MaxInt] of byte;
var
N: integer;
begin
N := 0;
while (N<size) and (Bytes(dest)[N] <> Bytes(source)[N]
do Inc(N);
Equal := N = size;
end;
Эта функция может использоваться для сравнения любых двух
переменных любого размера. Например, с помощью описаний:
type
Vector = array[1..10] of integer;
Point = record
x,y: integer;
end;
var
Vec1, Vec2: Vector;
N: integer;
P: Point;
и вызовов функций:
Equal(Vec1,Vec2,SizeOf(Vector))
Equal(Vec1,Vec2,SizeOf(integer)*N)
Equal(Vec[1],Vec1[6],SizeOf(integer)*5)
Equal(Vec1[1],P,4)
сравнивается Vес1 с Vес2, сравниваются первые N элементов Vес1 с
первыми N элементами Vес2, сравниваются первые 5 элементов Vес1 с
последними пятью элементами Vес2 и сравниваются Vес1[1] с Р.х и
Vес2[2] с P.Y.
Хотя нетипизированные параметры дают вам большую гибкость,
их использование сопряжено с некоторым риском. Компилятор не мо-
жет проверить допустимость операций с нетипизированными перемен-
ными.
Открытые параметры
Открытые параметры позволяют передавать одной и той же про-
цедуре или функции строки и массивы различных размеров.
Открытые строковые параметры
Открытые строковые параметры могут описываться двумя спосо-
бами:
- с помощью идентификатора OpenString;
- с помощью ключевого слова string в состоянии {$P+}.
Идентификатор OpenString описывается в модуле System. Он
обозначает специальный строковый тип, который может использовать-
ся только в описании строковых параметров. В целях обратной сов-
местимости OpenString не является зарезервированным словом и мо-
жет, таким образом, быть переопределен как идентификатор, задан-
ный пользователем.
Когда обратная совместимость значения не имеет, для измене-
ния смысла ключевого слова string можно использовать директиву
компилятора {$P+}. В состоянии {$P+} переменная, описанная с клю-
чевым словом string, является открытым строковым параметром.
Для открытого строкового параметра фактический параметр мо-
жет быть переменной любого строкового типа. В процедуре или функ-
ции атрибут размера (максимальная длина) формального параметра
будет тем же, что у фактического параметра.
Открытые строковые параметры ведут себя также как парамет-
ры-переменные строкового типа, только их нельзя передавать как
обычные переменные другим процедурам или функциям. Однако, их
можно снова передать как открытые строковые параметры.
В следующем примере параметр S процедуры AssignStr - это
открытый строковый параметр:
procedure AssignStr(var S: OpenString;
begin
S := '0123456789ABCDEF';
end;
Так как S - это открытый строковый параметр, AssignStr можно
передавать переменные любого строкового типа:
var
S1: string[10];
S1: string[20];
begin
AssignStr(S1); { S1 := '0123456789' }
AssignStr(S2); { S2 := '0123456789ABCDEF' }
end;
В AssingStr максимальная длина параметра S та же самая, что
у фактического параметра. Таким образом, в первом вызове
AssingStr при присваивании параметра S строка усекается, так как
максимальная длина S1 равна 10.
При применении к открытому строковому параметру стандартная
функция Low возвращает 0, стандартная функция High возвращает
описанную максимальную длину фактического параметра, а функция
SizeOf возвращает размер фактического параметра.
В следующем примере процедура FillString заполняет строку
заданным символом до ее максимальной длины. Обратите внимание на
использование функции High для получения максимальной длины отк-
рытого строкового параметра.
procedure FillStr(var S: OpenString; Ch: Char);
begin
S[0] := Chr(High(S)); { задает длину строки }
FillChar(S[1], High(S), Ch); { устанавливает число
символов }
emd;
Значения и параметры-константы, описанные с использованием
идентификатора OpenString или ключевого слова string в состоянии
{$P+}, не являются открытыми строковыми параметрами. Они ведут
себя также, как если бы были описаны с максимальной длиной стро-
кового типа 255, а функция Hingh для таких параметров всегда
возвращает 255.
Открытые параметры-массивы
Формальный параметр, описанный с помощью синтаксиса:
array of T
является открытым параметром-массивом. T должно быть идентифика-
тором типа, а фактический параметр должен быть переменной типа T,
или массивом, типом элементов которого является T. В процедуре
или функции формальный параметр ведет себя так, как если бы он
был описан следующим образом:
arra[0..N - 1] of T
где N - число элементов в фактическом параметре. По существу,
диапазон индекса фактического параметра отображается в диапазон
целых чисел от 0 до N - 1. Если фактический параметр - это прос-
тая переменная типа T, то он интерпретируется как массив с одним
элементом типа T.
К открытому формальному параметру-массиву можно обращаться
только по элементам. Присваивания всему открытому массиву не до-
пускаются, и открытый массив может передаваться другим процедурам
или функциям только как открытый параметр-массив или нетипизиро-
ванный параметр-переменная.
Открытый параметр-массив может быть параметром-значением,
параметром-константой и параметром-переменной и имеет тот же
смысл, что и обычные параметры-значения, параметры-константы и
параметры-переменные. В частности, присваивания элементам фор-
мального открытого массива-константы не допускаются, а присваива-
ния элементам формального открытого массива, являющегося парамет-
ром-значением, не влияют на фактический параметр.
Для открытых массивов-значений компилятор создает в кадре
стека процедуры или функции локальную копию фактического парамет-
ра. Таким образом, при передаче в качестве открытых парамет-
ров-значений больших массивов следует учитывать возможное пере-
полнение стека.
При применении к открытому параметру-массиву стандартная
функция Low возвращает 0, стандартная функция High возвращает ин-
декс последнего элемента в фактическом параметре-массиве, а функ-
ция SizeOf возвращает размер фактического параметра-массива.
Процедура Clear в следующем примере присваивает каждому эле-
менту массива вещественных значений ноль, а функция Sum вычисляет
сумму всех элементов массива вещественных чисел. Поскольку в обо-
их случаях параметр A является открытым параметром-массивом,
подпрограммы могут работать с любым массивом элементов типа Real:
procedure Clear(var A: array of Real);
var
I: Word;
begin
for I := 0 to High(A) do A[I] := 0;
end;
function Sum(const A: array of Real): Real;
var
I: Word;
S: Real;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I];
Sum := S;
end;
Когда типом элементов открытого параметра-массива является
Char, фактический параметр может быть строковой константой. Нап-
ример, с учетом предыдущего описания:
procedure PringStr(const S: array of Char);
var
I: Integer;
begin
for I := 0 to High(S) do
if S[I] <> #0 then Write(S[I]) else Break;
end;
и допустимы следующие операторы процедур:
PrintStr('Hello word');
PrintStr('A');
При передаче в качестве открытого параметра-массива пустая
строка преобразуется в строку с одним элементом, содержащим сим-
вол NULL, поэтому оператор PrintStr('') идентичен оператору
PrintStr('#0').
Динамические переменные объектного типа
Стандартные процедуры New и Dispose допускают в качестве
второго параметра вызов конструктора или деструктора для выделе-
ния для памяти переменной объектного типа или ее освобождения.
При этом используется следующий синтаксис:
New(P, Construct)
и
Dispose(P, Destruct)
где P - это указатель на переменную, ссылающийся на объектный
тип, а Construct и Destruct - это вызовы конструкторов и деструк-
торов объектного типа. Для New эффект расширенного синтаксиса тот
же, что и от выполнения операторов:
New(P);
P^.Construct;
а для Dispose это эквивалентно операторам:
P^.Dispose;
Dispose(P);
Без расширенного синтаксиса вам пришлось бы часто вслед за
вызовом конструктора вызывать New, или после вызова деструктора
вызывать Dispose. Расширенный синтаксис улучшает читаемость ис-
ходного кода и генерирует более короткий и эффективный код.
Приведенный пример иллюстрирует использование расширенного
синтаксиса New и Dispose:
var
SP: PStrField
ZP: PZipField
begin
New(SP, Init(1, 1, 25, 'Имя'));
New(ZP, Init(1, 2, 5, 'Почтовый индекс'), 0, 99999));
SP^.Edit;
ZP^.Edit;
.
.
.
Dispose(ZP, Done);
Dispose(SP, Done);
end;
Вы можете также использовать New как функцию, распределяющую
и возвращающую динамическую переменную заданного размера:
New(T)
или
New(T, Construct)
В первой форме T может быть любым ссылочным типом. Во второй
форме T должен указывать на объектный тип, а Construct должен
быть вызовом конструктора этого объекта. В обоих случаях типом
результата функции будет T.
Приведем пример:
var
F1, F2: PField
begin
F1 := New(PStrField, Init(1, 1, 25, 'Имя'));
F1 := New(PZipField, Init(1, 2, 5, 'Почтовый индекс', 0,
99999));
.
.
.
WriteLn(F1^.GetStr); { вызывает TStrField.GetStr }
WriteLn(F2^.GetStr); { вызывает TZipField.GetStr }
.
.
.
Dispose(F2, Done); { вызывает TField.Done }
Dispose(F1, Done); { вызывает TStrField.Done }
end;
Заметим, что хотя F1 и F2 имеют тип PField, правила совмес-
тимости по присваиванию расширенного указателя позволяют присваи-
вать F1 и F2 указателю на любой потомок TField. Поскольку GetStr
и Done являются виртуальными методами, механизм диспетчеризации
виртуального метода корректно вызывает, соответственно,
TStrString.GetStr, TZipField.GetStr, TField.Done и
TStrField.Done.
Процедурные переменные
После определения процедурного типа появляется возможность
описывать переменные этого типа. Такие переменные называют проце-
дурными переменными. Например, с учетом описаний типа из предыду-
щего примера, можно объявить следующие переменные:
var
P: SwapProc;
F: MathFunc;
Как и целая переменная, которой можно присвоить значение це-
лого типа, процедурной переменной можно присвоить значение проце-
дурного типа. Таким значением может быть, конечно, другая проце-
дурная переменная, но оно может также представлять собой иденти-
фикатор процедуры или функции. В таком контексте описания проце-
дуры или функции можно рассматривать, как описание особого рода
константы, значением которой является процедура или функция. Нап-
ример, пусть мы имеем следующие описания процедуры и функции:
procedure Swap(var A,B: integer);
var
Temp: integer;
begin
Temp := A;
A := B;
B := Temp;
end.
function Tan(Angle: real): real;
begin
Tan := Sin(Angle) / Cos(Angle);
end.
Описанным ранее переменным P и F теперь можно присвоить зна-
чения:
P := Swap;
F := Tan;
После такого присваивания обращение P(i,j) эквивалентно Swap
(i,j) и F(X) эквивалентно Tan(X).
Как и при любом другом присваивании, значения переменной в
левой и в правой части должны быть совместимы по присваиванию.
Процедурные типы, чтобы они были совместимы по присваиванию,
должны иметь одно и то же число параметров, а параметры на соот-
ветствующих позициях должны быть одинакового типа. Как упомина-
лось ранее, имена параметров в описании процедурного типа никако-
го действия не вызывают.
Кроме того, для обеспечения совместимости по присваиванию
процедура и функция, если ее нужно присвоить процедурной перемен-
ной, должна удовлетворять следующим требованиям:
- Это не должна быть стандартная процедура или функция.
- Такая процедура или функция не может быть вложенной.
- Такая процедура не должна быть процедурой типа inline.
- Она не должна быть процедурой прерывания (interrupt).
Стандартными процедурами и функциями считаются процедуры и
функции, описанные в модуле System, такие, как Writeln, Readln,
Chr, Ord. Чтобы получить возможность использовать стандартную
процедуру или функцию с процедурной переменной, вы должны напи-
сать для нее специальную "оболочку". Например, пусть мы имеем
процедурный тип:
type
IntProc = procedure(N: integer);
Следующая процедура для записи целого числа будет совмести-
мой по присваиванию:
procedure WriteInt(Number: Integer); far;
begin
Write(Number);
end.
Вложенные процедуры и функции с процедурными переменными ис-
пользовать нельзя. Процедура или функция считается вложенной,
когда она описывается внутри другой процедуры или функции. В сле-
дующем примере процедура Inner вложена в процедуру Outer и поэто-
му ее нельзя присваивать процедурной переменной:
program Nested;
procedure Outer;
procedure Inner;
begin
Writeln('Процедура Inner является вложенной');
end;
begin
Inner;
end;
begin
Outer;
end.
Использование процедурных типов не ограничивается просто
процедурными переменными. Как и любой другой тип, процедурный тип
может участвовать в описании структурного типа, что видно из сле-
дующих описаний:
type
GotoProc = procedure(X,Y: integer);
ProcList = array[1..10] of GotoProc;
WindowPtr = ^WindowRec;
Window = record
Next: WindowPtr;
Header: string[31];
Top,Left,Bottom,Right: integer;
SetCursor: GotoProc;
end;
var
P: ProcList;
W: WindowPtr;
С учетом этих описаний допустимы следующие вызовы процедур:
P[3](1,1);
W.SetCursor(10,10);
Когда процедурной переменной присваивается значение процеду-
ры, то на физическом уровне происходит следующее: адрес процедуры
сохраняется в переменной. Фактически, процедурная переменная
весьма напоминает переменную-указатель, только вместо ссылки на
данные она указывает на процедуру или функцию. Как и указатель,
процедурная переменная занимает 4 байта (два слова), в которых
содержится адрес памяти. В первом слове хранится смещение, во
втором - сегмент.
Параметры процедурного типа
Поскольку процедурные типы допускается использовать в любом
контексте, то можно описывать процедуры или функции, которые
воспринимают процедуры и функции в качестве параметров. В следую-
щем примере показывается использование параметров процедурного
типа для вывода трех таблиц различных арифметических функций:
program Tables;
type
Func = function(X,Y: integer): integer;
function Add(X,Y: integer): integer; far;
begin
Add := X + Y;
end;
function Multiply(X,Y: integer): integer; far;
begin
Multiply := X*Y;
end;
function Funny(X,Y: integer): integer; far;
begin
Funny := (X+Y) * (X-Y);
end;
procedure PrintTable(W,H: integer; Operation: Func);
var
X,Y : integer;
begin
for Y := 1 to H do
begin
for X := 1 to W do Write(Operation(X,Y):5);
Writeln;
end;
Writeln;
end;
begin
PrintTable(10,10,Add);
PrintTable(10,10,Multiply);
PrintTable(10,10,Funny);
end.
При работе программа Table выводит три таблицы. Вторая из
них выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
Параметры процедурного типа особенно полезны в том случае,
когда над множеством процедур или функций нужно выполнить ка-
кие-то общие действия. В данном случае процедуры PrintTable
представляет собой общее действие, выполняемое над функциями Add,
Multiply и Funny.
Если процедура или функция должны передаваться в качестве
параметра, они должны удовлетворять тем же правилам совместимости
типа, что и при присваивании. То есть, такие процедуры или функ-
ции должны компилироваться с директивой far, они не могут быть
встроенными функциями, не могут быть вложенными и не могут описы-
ваться с атрибутами inline или interrupt.
|