ГЛАВА 8. ПРОЦЕДУРЫ И ФУНКЦИИ. Процедуры и функции позволяют включать в основной программный блок дополнительные блоки. Каждое объявление процедуры или функции содержит заголовок, за которым следует блок. Процедура активизируется с помощью оператора процедуры. Функция активизируется при вычислении выражения, содержащего вызов функции и возвращаемое функцией значение подставляется в это выражение. В данной главе обсуждаются различные способы объявлений процедур или функций и их параметры. Объявление процедур. Объявление процедуры позволяет связать идентификатор с блоком процедуры. Процедуру можно активизировать с помощью оператора процедуры ┌─────────┐ ┌───┐ ┌─────────┐ ┌───┐ объявление ──Ў│заголовок├──Ў│ ; ├──Ў│ тело ├──Ў│ ; ├──Ў процедуры │процедуры│ └───┘ │процедуры│ └───┘ └─────────┘ └─────────┘ ┌─────────┐ ┌─────────────┐ заголовок ──Ў│procedure├──Ў│идентификатор├─┬──────────────────Ў процедуры └─────────┘ └─────────────┘ │ ┌──────────┐ ° │ │список │ │ └─Ў│формальных├─┘ │параметров│ └──────────┘ ┌──────┐ тело ───┬──────────────────────────────┬──Ў│модуль├────────Ў процедуры │ ┌─────────┐ ┌─┐ ° │ └──────┘ ° ├──Ў│ near ├─────Ў│;├───┘ │ ┌───────┐ │ │ └─────────┘ ° └─┘ │──Ў│forward├───┤ │ ┌─────────┐ │ │ └───────┘ │ ├──Ў│ far ├──┤ │ ┌────────┐ │ │ └─────────┘ │ │──Ў│external├──┤ │ ┌─────────┐ │ │ └────────┘ │ ├──Ў│interrupt├──┘ │ ┌────────┐ │ │ └─────────┘ └──Ў│блок asm├──┤ │ └────────┘ │ │ ┌─────────┐ │ └─────────────────────────────────Ў│директива├─┘ │ 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; near и far объявления. Turbo Pascal поддерживает 2 модели вызова процедур: near (ближняя) и far (дальняя). В терминах размера кода и скорости выполнения модель вызова near более эффективна, но существует ограничение - ближняя процедура может вызываться только внутри модуля, в котором она объявлена. С другой стороны, дальние процедуры могут вызываться из любого модуля, но код дальних вызовов несколько менее эффективен. Turbo Pascal автоматически выбирает корректную модель вызова на основании объявления процедуры: процедуры, объявленные в разделе interface модуля, используют дальнюю модель вызова - они могут быть вызваны из других модулей. Процедуры, объявленные в программе или в разделе implementation модуля, используют ближнюю модель вызова - они могут вызываться только внутри этой программы или модуля. В некоторых случаях может потребоваться использование для процедуры дальней модели вызова. Например, в оверлейных программах все процедуры и функции должны быть дальними; аналогично, если процедура или функция присваивается процедурной переменной, она должна использовать дальнюю модель вызова. Директива компилятора $F может использоваться для перекрытия автоматического выбора модели вызова компилятором. Процедуры и функции, откомпилированные в состоянии {$F+} всегда используют дальнюю модель вызова; в состоянии {$F-} компилятор автоматически выбирает корректную модель. Состояние по умолчанию - {$F-}. Для задания требуемой модели вызова объявление процедуры может содержать директиву near или far до блока - если такая директива указана, она перекрывает установку директивы компилятора $F, а так же автоматический выбор модели вызова компилятора. interrupt объявление. Объявление процедуры может содержать директиву interrupt перед блоком, и тогда процедура рассматривается как процедура прерывания. Процедуры прерывания полностью описаны в главе 18 "Вопросы контроля". Сейчас заметим, что процедуры прерывания нельзя вызывать с помощью операторов процедур и что каждая процедура прерывания должна задавать список параметров точно так, как это показано ниже: procedure MyInt (Flags, CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP : Word); interrupt; Вместо блока в объявлении процедуры или функции можно написать forward (опережающая), external (внешняя) или inline (встроенная) объявление. Опережающие объявления (forward). Опережающим (forward) объявлением называется объявление процедуры, задающее директиву forward вместо блока. Где-нибудь после этого объявления процедура должна быть определена с помощью определяющего объявления - объявления процедуры, которое использует такой же идентификатор процедуры, но опускает список формальных параметров и содержит блок. Опережающее объявление и определяющее объявление должны находиться в одной и той же части объявления процедур и функций. Между ними могут быть объявлены другие процедуры и функции, и они могут вызывать процедуру с опережающим объявлением. Таким образом, возможна взаимная рекурсия. Опережающее объявление и определяющее объявление составляют полное объявление процедуры. Считается, что процедура объявлена в опережающем объявлении. Приведем пример опережающего объявления: procedure Walter(M, N: Integer): forward; procedure Clara(X, Y: Real); begin ... Walter(4, 5); ... end; procedure Walter; begin ... Clara(8.3, 2.4); ... end; Определяющее объявление процедуры может быть external или assembler. Однако, оно не может быть near, far, inline или другим forward объявлением. Определяющее объявление также не может содержать директиву interrupt, near или far. Опережающие объявления не допускаются в интерфейсной части модуля. Внешние объявления (external). Внешние объявления позволяют связывать отдельно скомпилированные процедуры и функции, написанные на языке ассемблера. С помощью директивы {$L имя файла} внешнюю программу можно связать с программой или модулем, написанным на Паскале. Более детальные объявления редактирования связей с программой на языке ассемблера содержатся в главе 23. Приведем следующие примеры объявлений внешних процедур: procedure MoveWord(var Source, Dest; Count: Word); external; procedure MoveLong(var Source, Dest; Count: Word); external; procedure FillWord(var Dest; Data: Integer; Count: Word); external; procedure FillLong(var Dest; Data: Longint; Count: Word); external; {$L BLOCK.OBJ} assembler объявление. assembler объявление позволяет Вам написать процедуру или функцию на встроенном Ассемблере. Более детальное описание Ассемблерных процедур и функций приведено в главе 22 "Встроенный Ассемблер". ┌─────────┐ ┌─┐ ┌──────────┐ ┌────────────┐ блок asm ───Ў│assembler├──Ў│;├──Ў│ раздел ├──Ў│asm оператор├──Ў └─────────┘ └─┘ │объявления│ └────────────┘ └──────────┘ inline объявление. Директива inline позволяет записывать инструкции в машинном коде, не используя блок операторов. При вызове обычной процедуры компилятор создает код, в котором параметры процедуры помещаются в стек, а затем для вызова процедуры генерируется инструкция call. Когда вы вызываете внутреннюю процедуру, компилятор генерирует код из директивы inline вместо call. Таким образом, inline процедура "расширяется" при каждом обращении к ней, аналогично макро на языке ассемблера. Приведем два небольших примера внутренних процедур: procedure DisableInterrupts; inline ($FA); {CLI} procedure EnableInterrupts; inline ($FB); {STI} Объявления функций. Объявление функции определяет часть программы, в которой вычисляется и возвращается значение. ┌─────────┐ ┌─┐ ┌───────┐ ┌─┐ объявление ───Ў│заголовок├──Ў│;├──Ў│ тело ├──Ў│;├──Ў функции │ функции │ └─┘ │функции│ └─┘ └─────────┘ └───────┘ ┌────────┐ ┌─────────────┐ заголовок ───Ў│function├─Ў│идентификатор├──┬───────────────────┐ функции └────────┘ └─────────────┘ │ ┌──────────┐ ° │ │ │список │ │ │ └─Ў│формальных├──┘ │ │параметров│ │ └──────────┘ │ ┌─────────────────────┘ │ ┌─┐ ┌────────┐ └─Ў│:├──Ў│тип ре- ├────Ў └─┘ │зультата│ └────────┘ ┌─────────────┐ тип результата ─────Ў│идентификатор├─────────Ў │ │ типа │ ° │ └─────────────┘ │ │ ┌──────┐ │ └─────Ў│string├─────────┘ └──────┘ ┌──────┐ тело ───┬─────────────────────────┬──Ў│модуль├────────Ў функции │ ┌──────┐ ┌─┐ ° │ └──────┘ ° ├──Ў│ near ├────Ў│;├──┘ │ ┌───────┐ │ │ └──────┘ ° └─┘ ├──Ў│forward├───┤ │ ┌──────┐ │ │ └───────┘ │ ├──Ў│ far ├──┘ │ ┌────────┐ │ │ └──────┘ ├──Ў│external├──┤ │ │ └────────┘ │ │ │ ┌────────┐ │ │ └──Ў│блок asm├──┤ │ └────────┘ │ │ ┌─────────┐ │ └────────────────────────────Ў│директива├─┘ │ inline │ └─────────┘ Заголовок функции содержит идентификатор для функции, формальные параметры (если они присутствуют) и тип результата функции. Функция активизируется с помощью вызова функции или вычисления. Вызов функции содержит идентификатор функции и необходимые фактические параметры. Функция выполняется при вычислении выражения, а значение операнда становится значением, возвращаемым функцией. Операторная часть блока функции содержит операторы, которые выполняются при активизации функции. Блок должен содержать по крайней мере один оператор присваивания, в котором присваивается значение идентификатору функции. Результатом выполнения функции будет последнее присваиваемое значение. Если такого оператора присваивания нет или он не выполняется, то возвращаемое функцией значение будет неопределенным. Если идентификатор функции используется для вызова функции внутри блока функции, то функция выполняется рекурсивно. Приведем пример объявлений функций: 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; I := I div 2; X := Sqr(X); end; Power := Z; end; Как и процедуры, функции могут быть объявлены как forward, external, inline, near, far и assembler; однако не допустимы 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.Y < 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 Field.Copy(var F: Field); begin Self := F; end; constructor Field.Init(FX, FY, FLen: Integer; FName: String); begin X := FX; Y := FY; Len := FLen; GetMem(Name, Length(FName) + 1); Name^ := FName; end; constructor StrField.Init(FX, FY, FLen: Integer; Fname : String); begin Field.Init(FX, FY, FLen, FName); GetMem(Value, Len); Value^ := ' '; end; Первое действие констрактора порожденного типа такого, как StrField.Init в примере - почти всегда вызов констрактора предка для инициализации унаследованных полей объекта. После этого констрактор инициализирует поля объекта, которые были введены в порожденном типе. Примечание: Дестракторы могут быть виртуальными. Дестракторы редко имеют параметры. Дестракторы выполняют действие, противоположное констракторам и используются для очистки объектов после их использования. Очистка обычно состоит в освобождении всех полей - указателей объекта. Примеры дестракторов: destructor Field.Done; begin FreeMem(Name, Length(Name^) + 1); end; destructor StrField.Done; begin FreeMem(Value, Len); Field.Done; end; Дестрактор порожденного типа такой как StrField.Done в примере, обычно в начале уничтожают поля указателей, введенные в нем, и затем, в качестве последнего действия, вызывают соответствующий дестрактор своего непосредственного предка для освобождения всех унаследованных полей указателей объекта. Параметры. Объявление процедуры или функции содержит список формальных параметров. Каждый параметр, объявленный в списке формальных параметров, является локальным по отношению к объявленной процедуре или функции, на него можно сделать ссылку с помощью идентификатора в блоке, связанном с процедурой или функцией. ┌─┐ ┌──────────┐ ┌─┐ список формальных ───Ў│(├─────Ў│объявление├──┬──Ў│)├──Ў параметров └─┘ ° │параметра │ │ └─┘ │ └──────────┘ │ │ ┌─┐ │ └──────┤;│ў───────┘ └─┘ ┌─────────────┐ объявление ──┬──────────Ў│список иден- ├──┬──────────────────────Ў параметра │ ° │тификаторов │ │ ° │ │ └─────────────┘ │ │ │ ┌───┐ │ │ │ └─Ў│var├─┘ │ ┌─┐ ┌───────┐ │ └───┘ └─Ў│:├──Ў│тип па-├─┘ └─┘ │раметра│ └───────┘ ┌──────────┐ тип параметра ───┬──Ў│ идентифи-├───────Ў │ │катор типа│ ° │ └──────────┘ │ │ ┌────────┐ │ ├──Ў│ string ├─────┤ │ └────────┘ │ │ ┌──────┐ │ └──Ў│ file ├───────┘ └──────┘ Существует три вида параметров: значение, переменная и нетипированная переменная. Они характеризуются следующим: - группа параметров без предшествующего ключевого слова var и со следующим за ней типом является списком параметров - значений. - группа параметров с предшествующим ключевым словом var и следующим за ней типом является списком параметров - переменных. - группа параметров с предшествующим ей ключевым словом var и без следующего за ней типа является списком нетипированных параметров переменных. Параметры - значения. Формальный параметр-значение действует как переменная, локальная по отношению к процедуре или функции за исключением того, что она получает свое исходное значение из соответствующего фактического параметра при активизации процедуры или функции. Изменения формального параметра-значения не влияют на значение фактического параметра. Фактический параметр, соответствующий параметру-значению в операторе процедуры или вызове функции должен быть выражением, а его значение не может быть файлового типа или какого-нибудь структурного типа. Фактический параметр должен быть совместим по присваиванию с типом формального параметра-значения. Если типом параметра является String, то формальный параметр будет иметь атрибут размера, равный 255. Параметр - переменная. Если значение нужно передать из процедуры или функции в вызывающую программу, то используется параметр-переменная. Соответствующий фактический параметр в операторе вызова процедуры или функции должен быть ссылкой на переменную. Формальный параметр-переменная представляет фактическую переменную во время активизации процедуры или функции, поэтому все изменения значения формального параметра отражаются на фактическом параметре. Внутри процедуры или функции любая ссылка на формальный параметр-переменную дает доступ к самому фактическому параметру. Тип фактического параметра должен быть тождественен типу формального параметра-переменной (можно обойти это ограничение через нетипированные параметры-переменные). Если типом формального параметра является String, то задается атрибут длины, равный 255, и фактический параметр-переменная должен иметь строковый тип с атрибутом длины, равным 255. Файловые типы могут передаваться только как параметры-переменные. Если ссылка на фактический параметр-переменную связана с индексированием массива или нахождением объекта по его указателю, то эти действия выполняются до активизации процедуры или функции. Объекты. Правило совместимости по присваиванию объектных типов так же применимо к параметрам-переменным объектных типов: для формального параметра типа Т1 действительный параметр может быть типа Т2, если Т2 лежит в области определения Т1. Например: методу FieldCopy может быть передан экземпляр от Field, StrField, NumField, ZipField или любой другой экземпляр, порожденный от Field. Нетипированные параметры-переменные. Когда формальный параметр является нетипированным параметром-переменной, соответствующий фактический параметр может быть любой ссылкой на переменную независимо от ее типа. Внутри процедуры или функции нетипированный параметр-переменная не имеет типа, то есть он не совместим с переменными всех других типов, если ему не присвоен определенный тип с помощью приведения типа переменной. Приведем пример нетипированных параметров-переменных: 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, Y) Сравнивается Vec1 c Vec2, сравниваются первые N компонент Vec1 с первыми N компонентами Vec2, сравниваются первые пять компонент Vec1 с последними пятью компонентами Vec1, сравниваются Vec1[1] с P.X и Vec1[2] с P.Y. Процедурные типы. В качестве расширения стандартного Паскаля, Turbo Pascal позволяет рассматривать процедуры и функции как объекты, которые можно присвоить переменным и которые могут выступать в качестве параметров; процедурные типы делают это возможным (процедурные типы определены в главе 3 "Типы"). Процедурные переменные. Как только процедурный тип определен, можно объявлять переменные этого типа. Такие переменные называются процедурными переменными. Например, используя данное выше объявление типа, можно описать следующие переменные: var P: SwapProc; F: MathFunc; Подобно тому, как целочисленной переменной можно присвоить целочисленное значение, процедурной переменной можно присвоить процедурное значение. Такое значение может, конечно, быть и другой процедурной переменной, но может также и быть идентификатором процедуры или функции. В этой ситуации объявление процедуры или функции можно рассматривать как особый вид объявления константы, значением которой является процедура или функция. Например, даны следующие объявления процедур и функций: {$F+} 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. {$F-} Переменным P и F, объявленным прежде, можно присвоить значения: P := Swap; T := Tan; Согласно этим присваиваниям, вызов P(I, J) эквивалентен вызову Swap(I, J), а вызов F(X) эквивалентен вызову Tan(X). Как и во всех других операциях присваивания, переменная в левой части и переменная в правой части оператора присваивания должны быть совместимыми по присваиванию. Для того чтобы считаться совместимыми по присваиванию, процедурные типы должны иметь одинаковое число параметров, параметры в соответствующих позициях должны быть тождественных типов; наконец, типы результатов функций должны быть идентичны. Как отмечено выше, названия параметров не имеют значения, когда проверяется совместимость типа процедуры. Дополнительно к совместимости типов, процедура или функция должны удовлетворять следующим требованиям, если они присваиваются процедурной переменной. - Она должна быть объявлена с директивой far и откомпилирована в состоянии {$F+} - Она не должна быть - стандартной процедурой или фукцией - вложенной процедурой или функцией - inline процедурой или функцией - interrupt процедурой или функцией Стандартные процедуры и функции, такие как WriteLn, ReadLn, Chr, и Ord, объявлены в модуле System. Для того чтобы использовать стандартную процедуру или функцию с процедурной переменной, Вы должны окружить ее "оболочкой". Например, задан тип процедуры type IntProc = procedure(N: Integer); далее приведена совместимая по присваиванию процедура для вывода целого числа: procedure WriteInt(Number: Integer); far; begin write(Number); end; Вложенные процедуры или функции нельзя использовать с процедурными переменными. Процедура или функция называется вложенной, если она объявлена внутри другой процедуры или функции. В следующем примере Inner вложена в Outer, следовательно Inner нельзя присвоить процедурной переменной. program Nested; procedure Outer; procedure Inner; begin WriteLn('Inner is nested'); 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; {$F+} function Add(X, Y: Integer): Integer; begin Add := X + Y; end; Function Multiply(X, Y: Integer): Integer; begin Multiply := X * Y; end; function Funny(X, Y: Integer): Integer; begin Funny := (X + Y) * (X - Y); end; {$F-} procedure PrintTable(W, H: Integer; Operation: Func); var X, Y: Integer; begin for Y := 1 to H do begin for X := 1 to H do Write(Operation(X,Y ) : 5); Writeln; end; Writeln; end; begin PrintTable(10, 10, Add); PrintTable(10, 10, Multiply); PrintTable(10, 10, Funny); end. При выполнениии программа Tables выводит три таблицы. Вторая таблица будет выглядеть следующим образом : 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 атрибутами.