назад

Глава 9. Процедуры и функции

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

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

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


Описания процедур

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

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

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

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

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

     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;

Описание 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.


Описания функций

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

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

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

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

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

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

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

     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) не допускаются.


Параметры

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

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

  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< (Bytes(dest)[N] and> 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 описывается в модуле 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').


Процедурные переменные

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

     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).

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

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

Стандартными процедурами и функциями считаются процедуры и функции, описанные в модуле System, такие, как Writeln, Readln, Chr, Ord. Чтобы получить возможность использовать стандартную процедуру или функцию с процедурной переменной, вы должны написать для нее специальную "оболочку". Например, пусть мы имеем процедурный тип:

<PRE< pre integer);< IntProc="procedure(N:" type>

Следующая процедура для записи целого числа будет совместимой по присваиванию:

     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.

 

Hosted by uCoz