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

Примечание: Определение блока вы можете найти в  Главе  8 "Блоки, локальность и область действия".

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

Описание процедуры позволяет связать идентификатор с  процедурным  блоком.  Процедуру можно  затем активизировать с помощью оператора процедуры.

ЪДДДДДДДДДї   ЪДДДї  ЪДДДДДДДДДДДДї   ЪДДДї

   описание ДДД>ізаголовокГДД>і ; ГДД>і тело ГДД>і ; ГДД>

   процедуры іпроцедурыі   АДДДЩ   іподпрограммыі   АДДДЩ

АДДДДДДДДДЩ  АДДДДДДДДДДДДЩ

 

ЪДДДДДДДДДї   ЪДДДДДДДДДДДДДї

   заголовок ДД>іprocedureГДВ>іидентификаторГДДї

   процедуры АДДДДДДДДДЩ і АДДДДДДДДДДДДДЩ ^ГДДДДДДДДДДДДДДДДДД>

і ЪДДДДДДДДДДДДДї іі  ЪДДДДДДДДДДї ^

і і уточненный  і іі  і список  і і

А>іидентификаторГДЩАД>іформальныхГДЩ

   і  метода і  іпараметрові

  АДДДДДДДДДДДДДЩ  АДДДДДДДДДДЩ

 

  ЪДДДДДДї

   блок  ДДДВДВДД>імодульГДДДДДДДД>

   подпрограммыі   ЪДДДДДДДДДї   ЪДДДї   ^ і   АДДДДДДЩ^

   ГДД>і near ГДДДДД>і ; ГДДДЩ і   ЪДДДДДДДї  і

   і   АДДДДДДДДДЩ  ^   АДДДЩ  іДД>іforwardГДДДґ

   і   ЪДДДДДДДДДї  і і   АДДДДДДДЩ  і

   ГДД>і far  ГДДґ і   ЪДДДДДДДДДї і

   і   АДДДДДДДДДЩ  ііДД>ідирективаГДґ

   і   ЪДДДДДДДДДї  і і   і externalі і

   ГДД>і export ГДДґ і   АДДДДДДДДДЩ і

   і   АДДДДДДДДДЩ  і і   ЪДДДДДДДДї і

   і   ЪДДДДДДДДДї  і АДД>іблок asmГДДґ

  ГДД>іinterruptГДДЩ АДДДДДДДДЩ  і

   і   АДДДДДДДДДЩ  ЪДДДДДДДДДї і

  АДДДД>ідирективаГДЩ

  і inline  і

     АДДДДДДДДДЩ

Заголовки процедур  именуют идентификаторы процедур и задают  формальные параметры (если они имеются).

Примечание: Синтаксис списка формальных параметров показан далее в этой главе в разделе "Параметры".

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

Приведем пример описания процедуры:

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;

Borland Pascal  поддерживает  две  модели  вызова процедур -  ближнюю (near) и дальнюю (far). С точки зрения объема программы и  скорости выполнения ближняя модель вызова более эффективна,  но с  ней связаны ограничения:  процедуры типа  near  могут  вызываться  только в том модуле,  где они описаны. Процедуры же с дальним типом вызова можно вызывать из любого модуля,  но они несколько менее эффективны.

Примечание: О вызовах ближнего и дальнего типа рассказывается в Главе 22 "Вопросы управления".

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

Для некоторых  специальных целей может потребоваться использовать модель с дальним типом вызова.  Например, в оверлейных задачах обычно требуется, чтобы все процедуры и функции имели дальний тип  вызова.  Аналогично,   если   процедура   или  функция присваивается процедурной переменной, то она также должна использовать дальний тип вызова.  Чтобы переопределить  автоматический  выбор модели  вызова компилятором,  можно использовать директиву  компилятора {$F+}. Процедуры и функции, компилируемые в состоянии  {$F+}, всегда будут иметь дальний тип вызова (far), а в состоянии  {$F-} компилятор автоматически  выбирает корректную  модель.  По  умолчанию используется директива {$F-}.

Чтобы задать конкретную модель вызова,  в описании процедуры  перед ее блоком можно указать директиву near или far. При наличии  такой директивы она переопределяет директиву $F компилятора и автоматический выбор модели вызова.

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

Процедуры и функции должны быть экспортируемыми в  следующих  случаях:

* Процедуры и функции экспортируются DLL (динамически компонуемой библиотекой).

* Процедуры и функции системного вызова в программе Windows.

О том, как экспортировать процедуры и функции в DLL, рассказывается в  Главе  11 "Динамически компонуемые библиотеки".  Хотя  процедура и функция компилируется с директивой export,  фактический экспорт процедуры или функции не происходит, пока подпрограмма не перечисляется в операторе exports библиотеки.

Процедуры и функции системного вызова - это те  процедуры  и  функции вашей  прикладной программы,  которые  вызываются самой  Windows, а не вашей прикладной программой. Подпрограммы системного вызова должны компилироваться с директивой export, но в операторе exports их перечислять не нужно.  Приведем некоторые примеры  процедур и функций системного вызова:

* процедуры Windows;

* диалоговые процедуры;

* процедуры системного вызова для перечисления;

* процедуры уведомления об обращении к памяти;

* специализированные процедуры Windows (фильтры).

Borland Pascal автоматически генерирует для процедур и функций, экспортируемых программой Windows, эффективные системные вызовы. Эффективные  вызовы  ослабляют  необходимость использования  при создании  подпрограмм системного  вызова   подпрограмм   API  Windows MakeProcInstance иFreeProcInstance.

Примечание: См. раздел "Код входа и выхода" в Главе 22.

В описании процедуры перед блоком операторов может  указывается директива interrupt.  Процедура в этом случае рассматривается, как   процедура  прерывания.  Отметим  пока, что  процедура  interrupt не может вызываться из операторов процедуры, и что каждая процедура interrupt должна определять список параметров, например, следующим образом:

procedure MyInt(Flags,  CS, IP, AX, BX, CX, DX, SI, DI, DS,

ES, BP: Word);

interrupt;

Примечание: Не  используйте директиву  interrupt  при  разработке программ для Windows - это приведет к сбою.

Список параметров  не обязательно должен совпадать с указанным синтаксисом - он может быть короче и использовать другие имена, но регистры должны передаваться в указанном порядке.

Описание процедуры,  содержащее вместо блока операторов директиву 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 позволяют также импортировать процедуры и  функции  из DLL.

Примечание: Более   детальное описания  компоновки  с  программой на языке ассемблера содержится в Главе 25.

   директива external

   і ЪДДДДДДДДДДї

   АД>і external ГВДДДДДДДДДДДДДДДДДД>

   АДДДДДДДДДДЩі ЪДДДДДДДДДДДДДДДДДДДї  ^

   А>істроковая константаГВДДДДДДДДДДДДДДДДДДДДДДДЩ

 АДДДДДДДДДДДДДДДДДДДЩі ЪДДДДДДї ЪДДДДДДДДДї^

 Г>і name ГД>істроковаяГґ

 і АДДДДДДЩ  іконстантаіі

 і  АДДДДДДДДДЩі

 і ЪДДДДДДДї ЪДДДДДДДДДїі

 А>і index Г>і  целая  ГЩ

    АДДДДДДДЩ іконстантаі

 АДДДДДДДДДЩ

Директива 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 позволяют вам написать всю  процедуру или  функцию на ассемблере.

Примечание: Более  подробно о процедурах и функциях на Ассемблере рассказывается в Главе 24 "Встроенный ассемблер".

ЪДДДДДДДДДї   ЪДДДї  ЪДДДДДДДДДДї   ЪДДДДДДДДДДДДї

  блок asm Д>іassemblerГДД>і ; ГДД>і  раздел  ГДД>іasm операторГД>

АДДДДДДДДДЩ   АДДДЩ  і описания і   АДДДДДДДДДДДДЩ

 АДДДДДДДДДДЩ

Директивы inline  позволяют записывать вместо блока операторов инструкции в машинном коде. При вызове обычной процедуры компилятор  создает код,  в котором параметры процедуры помещаются в  стек, а затем для вызова процедуры генерируется инструкция CАLL.

 ЪДДДДДДДДДДДДДДДДДї

   директива inline ДД>і оператор inline ГДДДДДДДДДД>

 АДДДДДДДДДДДДДДДДДЩ

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

procedure DisableInterrupts: inline($FA);  { CLI }

procedure EnableInterrupts;  inline($FB);  { STI }

Примечание: Синтаксические диаграммы оператора  inline  описаны подробно в Главе 25.

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

  ЪДДДДДДДДДї   ЪДДДї  ЪДДДДДДДї   ЪДДДї

   описание  ДДД>ізаголовокГДД>і ; ГДД>і тело  ГДД>і ; ГДД>

   функции і функции і   АДДДЩ   іфункцииі   АДДДЩ

  АДДДДДДДДДЩ  АДДДДДДДЩ

 

  ЪДДДДДДДДї  ЪДДДДДДДДДДДДДї

   заголовок ДДД>іfunctionГВ>іидентификаторГДДВДДДДДДДДДДДДДДДДДДДї

   функции    АДДДДДДДДЩі АДДДДДДДДДДДДДЩ^ і  ЪДДДДДДДДДДї  ^  і

   і ЪДДДДДДДДДДДДДїі і  ісписок і і  і

   А>і уточненный  ГЩ АД>іформальныхГДДЩ  і

 іидентификаторі  іпараметрові  і

  і  метода і  АДДДДДДДДДДЩ  і

 АДДДДДДДДДДДДДЩЪДДДДДДДДДДДДДДДДДДДДДЩ

 і  ЪДДДї   ЪДДДДДДДДї

  АД>і : ГДД>ітип ре- ГДД>

 АДДДЩ   ізультатаі

АДДДДДДДДЩ

   ЪДДДДДДДДДДДДДї

   тип результата ДДВДД>іидентификаторГДДДДДДДДД>

  і   ітипа  і  ^

  і   АДДДДДДДДДДДДДЩ  і

  і   ЪДДДДДДї   і

 АДДДДД>іstringГДДДДДДДДДЩ

   АДДДДДДЩ

Примечание: Функция  не может  возвращать процедурный  тип или структурный тип.

В заголовке функции определяется идентификатор функции, формальные параметры (если они имеются) и тип результата функции.

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

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

Если идентификатор функции используется при  вызове  функции  внутри модуля-функции, то функция выполняется рекурсивно.

Приведем далее примеры описаний функции:

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,  конструкторы и деструкторы  обладают способностью размещения и удаления  динамических  объектов. Кроме того, конструкторы имеют возможность выполнить требуемую инициализацию объектов,  содержащих виртуальные методы. Как и  все другие методы,  конструкторы и деструкторы могут наследоваться, а объекты могут содержать любое число конструкторов и  деструкторов.

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

  ЪДДДДДДДДДДДДї   ЪДДДї  ЪДДДДДДДДДДДДї   ЪДДДї

   описание  ДДД>і  заголовок ГДД>і ; ГДД>і блок    ГДД>і ; ГД>

   конструктора  іконструктораі   АДДДЩ  іподпрограммыі   АДДДЩ

  АДДДДДДДДДДДДЩ  АДДДДДДДДДДДДЩ

 

   ЪДДДДДДДДДДДї  ЪДДДДДДДДДДДДДї

   заголовок ДДДД>іconstructorГВ>іидентификаторГДВДДДДДДДДДДДДДДДД>

   конструктора   АДДДДДДДДДДДЩі АДДДДДДДДДДДДДЩ^і  ЪДДДДДДДДДДї ^

і ЪДДДДДДДДДДДДДїіі  і  список і і

А>і уточненный  ГЩАД>іформальныхГДЩ

  іидентификаторі іпараметрові

   і  метода і АДДДДДДДДДДЩ

  АДДДДДДДДДДДДДЩ

Приведем несколько примеров конструкторов:

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 содержится внутри выражения, так что  можно проверить успешность выполнения наследуемого конструктора.

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

    ЪДДДї  ЪДДДДДДДДДДї   ЪДДДї

   список формальных ДДД>і ( ГДДДДД>і описание ГДДВДД>і ) ГДД>

   параметров   АДДДЩ  ^   іпараметра і  і   АДДДЩ

  і   АДДДДДДДДДДЩ  і

  і   ЪДДДї   і

  АДДДДДДґ ; і<ДДДДДЩ

  АДДДЩ

 

 ЪДДДДДДДДДДДДДї

   описание  ДДВДДДДДДДДДДДД>ісписок иден- ГВДДДДДДДДДДДДДДДДДДДДД>

   параметра   і  ЪДДДї ^ ітификаторов  іі   ^

   ГД>іvarГДДДДґ АДДДДДДДДДДДДДЩі ЪДДДї  ЪДДДДДДДї і

   і  АДДДЩі А>і : ГД>ітип па-ГДЩ

   і  ЪДДДДДї  і   АДДДЩ іраметраі

   АД>іconstГДДЩ АДДДДДДДЩ

   АДДДДДЩ

Существует три типа параметров: значение, переменная и нетипизированная переменная. Они характеризуются следующим:

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.