Помощь - Поиск - Пользователи - Календарь
Полная версия: Процедурные типы
Форум «Всё о Паскале» > Pascal, Object Pascal > Задачи > FAQ
volvo
Процедурные типы и переменные

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

Например, становится возможным написать функцию интегрирования любой функции вида f(t) по схеме:
function Integral(LowerLimit, UpperLimit: Real;
Funct: FuncType): Real;
{
Описание локальных переменных процедуры
}
var t: Real;
begin
{
Численное интегрирование по t от LowerLimit до UpperLimit
функции Funct, причем для получения значения функции при
заданном аргументе t достаточно сделать вызов Funct(t).

Результат интегрирования возвращается как результат функции Integral
}
end;


Характерно, что синтаксис записи процедурного типа в точности совпадает с записью заголовка процедуры или функции, только опускается идентификатор после ключевого слова procedure или function. Приведем некоторые примеры описаний процедурного типа (Turbo/Borland Pascal не позволяет описывать функции, которые возвращают значения процедурного типа. Результат функции должен быть строкового, вещественного, целого, символьного, булевского типа, указателем или иметь перечислимый тип, определенный пользователем):
type
Proc = procedure;
SwapProc = procedure(var X, Y: Integer);
StrProc = procedure(S: String);
MathFunc = function(X: Real): Real;
DeviceFunc = function(var F: text): Integer;
MaxFunc = function(A, B: Real; F: MathFunc): Real;


Как видно из приведенных примеров, существует два процедурных типа: тип-процедура и тип-функция.

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

В приведенном выше каркасе примера интегрирования есть одноместная функция F(t) возвращающая вещественное значение. Класс таких функций может описываться так:
type FuncType = function(t: Real): Real;


Тип, к которому могла бы принадлежать сама функция Integral, должен был бы выглядеть примерно так:
type
IntegralType = function(a, b: Real; f: FuncType): Real;


После объявления процедурного (или функционального) типа его можно использовать в описаниях параметров подпрограмм. И, конечно, необходимо написать те реальные процедуры и функции, которые будут передаваться как параметры. Требование к ним одно: они должны компилироваться в режиме {$F+}. Поскольку по умолчанию принят режим {$F-}, такие процедуры обрамляются парой соответствующих директив. Пример функции, которая принадлежит введенному выше типу FuncType:
{$F+}
function SinExp(tt: Real): Real;
begin
SinExp := Sin(tt) * Exp(tt)
end;
{$F-}

аналогичное описание с использованием директивы компилятора Far:
function SinExp(tt: Real): Real; far;
begin
SinExp := Sin(tt) * Exp(tt)
end;


Такая функция уже может быть подставлена в вызов функции численного интегрирования:
var x: Real;
...
x := Integral(0, 1, SinExp);

И мы получим в переменной X значение интеграла в пределах [0, 1].
Но не всякую функцию процедуру можно подставить в такой вызов. Существуют определенные правила.

Правила корректной работы с процедурными типами
  1. Подпрограмма, присваиваемая процедурной переменной должна быть оттранслирована в режиме ”дальнего вызова” (с использованием ключа компилятора {$F+} или директивы компилятора Far).
  2. Подпрограмма, присваиваемая процедурной переменной, не должна быть стандартной процедурой или функцией. Нельзя, например, напрямую взять интеграл функции синуса:
    x := Integral(0, 1, Sin);


    Это ограничение легко обойти, определив свою функцию:
    function MySin(R: Real): Real; far;
    begin
    MySin := Sin( R )
    end;
    ...
    begin
    ...
    x := Integral(0, 1, MySin);
    ...
    end.

  3. Подпрограмма, присваиваемая процедурной переменной, не может быть вложенной в другие подпрограммы.
  4. Подпрограмма, присваиваемая процедурной переменной, не может быть подпрограммой специального вида (interrupt или inline) из-за особенностей машинного представления.
Применение процедурного типа не ограничивается одним описанием параметров-процедур или функций. Если есть такой тип, то могут быть и переменные такого типа.
volvo
Процедурные переменные

Пример описания переменной процедурного типа:
{$F+}
var P: procedure;


Значениями Р могут быть любые процедуры без параметров. В более общем случае:

type
Func = function(X,Y: Integer): Integer;
var
F1, F2: Func;


Например, если есть функция:

function Add(A, B: Integer): Integer;
begin
Add:= A + B;
end;
, то допустимо F1 := Add, в этом случае переменной F1 присваивается функция Add как таковая, но её исполнения не происходит. Теперь можно:
Write(Add(1, 2));
{ или }
Write(F1(1, 2));


Следует обратить внимание на строку {$F+} – она существенна, это ключ компилятора, и если он выключен, при присвоении переменным процедурного типа значений конкретных подпрограмм, возникнет ошибка присвоения типа. (Исключения составляют функции, описанные в Interface-разделе модулей, так как они автоматически компилируются в режиме {$F+}, то есть в расчете на дальний вызов)

Процедурные типы допускают также присвоение вида F1 := F2;

Такие переменные можно использовать для вызова подпрограмм, которые присвоены этим переменным.
Пример
type
DemoProcType = procedure(a, b: Word);
var
P1, P2: DemoProcType;
P: Pointer;

{$F+}
procedure Add(A, B: Word);
begin
WriteLn('a + b = ', A + B);
end;

procedure Sub(A, B: Word);
begin
WriteLn('a - b = ', A - B);
end;
{$F-}

begin
P1 := Add; P2 := Sub;
P1(1, 1); { то же самое, что вызов Add(1, 1); }
P2(2, 2); { то же самое, что вызов Sub(2, 2); }

DemoProcType(P) := P1;
DemoProcType(P)(1, 1); { то же самое, что вызов P1(1, 1); }

@P2 := P;
P2(2, 2); { процедура P2 в итоге стала равна процедуре P1 }
end.


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

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

Приведенный выше пример был построен для наглядности, т.к. нет необходимости вводить переменные-процедуры там, где вместо них можно подставить обычные вызовы. Однако, использование процедурных типов не ограничивается простыми процедурными переменными. Как и любой другой тип, процедурные типы могут участвовать в построении структурированных типов (например, записи):
type
Proc = procedure(A, B: Word);
Notice = record
A, B: Integer;
Op: Proc;
end;
var
Rec1, Rec2: Notice;

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

Процедурная переменная занимает в памяти 4 байта (2 слова). В первом хранится смещение, во втором - сегмент (т.е. указатель на код подпрограммы).

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

К сожалению есть неопределённость для компилятора, например в таком случае:
type
Func = function: Real;
var
F: Func;

function FFF: Real;
begin
FFF:= 1.25;
end;
function FF: Real;
begin
FF:= 2.10;
end;

...
F := FF;
if F = FFF then ...


В подобных случаях неочевидно, должен ли компилятор сравнивать значение процедуры F с FFF или нужно вызвать процедуру F и FFF и сравнить их значения. Принято, что такое вхождение идентификатора подпрограммы означает вызов функции.

Чтобы сравнить значение переменной F со значением (адресом) подпрограммы FFF нужно использовать следующую конструкцию:
if @F = @FFF then ...

Чтобы получить адрес самой процедурной переменной нужно написать:
@@F


Приведение типов переменных для процедурных типов

Пусть определены следующие типы и переменные:
type
Func = function(X: Integer): Integer;

function MyFunc(X: Integer): Integer;
begin
MyFunc := X;
end;

var
F: Func;
P: Pointer;
N: Integer;


С их помощью можно построить следующие присваивания:

F := MyFunc;		{ переменной F присваивается функция MyFunc }
N := F(N); { функция MyFunc вызывается через переменную F }
P := @F; { P получает указатель на функцию MyFunc }
N := Func(P)(N); { функция MyFunc вызывается через указатель P }
F := Func(P); { присвоить значение подпрограммы в P переменной F }
Func(P) := F; { присвоить значение подпрограммы в F указателю P }
@F := P; { присвоить значение указателя в P переменной F }


Пример использования процедурных типов
Рассмотрим всем известный алгоритм "пузырьковой" сортировки.
const
n = 10;
type
arrType = array[1 .. n] of Integer;

procedure Bubble(var ar: arrType; n: integer);
var i, j, T: Integer;
begin
for i := 1 to n do
for j := n downto i+1 do
if ar[Pred(j)] > ar[j] then begin
T := ar[Pred(j)]; ar[Pred(j)] := ar[j]; ar[j] := T
end;
end;

const
a: arrType = (1, 4, 2, 6, 4, 2, 8, 10, 3, 4);
begin
Bubble(a, n);
end.


Этот вариант сортирует исходный массив по возрастанию. Для того, чтобы отсортировать его по убыванию, мы должны в строке
if ar[Pred(j)] > ar[j] then ...
изменить знак с "больше" на "меньше". Но зачем же мы будем изменять процедуру, тем более, что нам может пригодится сортировка как по возрастанию, так и по убыванию (возможно, даже в пределах одной программы).

Эта проблема очень просто решается с использованием функционального типа:
  • Заменяем сравнение элементов на вызов функции, которая будет производить сравнение.
  • Описываем 2 функции (одну для сортировки возрастающей, другую - для убывающей).
  • Изменяем заголовок процедуры сортировки, чтобы иметь возможность передать нужную нам в данный момент функцию.
Для сравнения 2-х значений будем вместо выражения
if ar[Pred(j)] > ar[j] then ...
пользоваться конструкцией:
type SortDirection = function(a, b: Real): Boolean;

function SortUp(a, b: Real): Boolean; far;
begin
SortUp := (a > b)
end;
function SortDown(a, b: Real): Boolean; far;
begin
SortDown := (a < b)
end;

и добавим этот фрагмент в программу. Окончательная версия будет выглядеть вот так:
type
SortDirection = function(a, b: Real): Boolean;

function SortUp(a, b: Real): Boolean; far;
begin
SortUp := (a > b)
end;
function SortDown(a, b: Real): Boolean; far;
begin
SortDown := (a < b)
end;

const n = 10;
type
arrType = array[1 .. n] of Integer;

procedure Bubble(var ar: arrType; n: integer; Order: SortDirection);
var i, j, T: Integer;
begin
for i := 1 to n do
for j := n downto i + 1 do
if Order(ar[Pred(j)], ar[j]) then begin
T := ar[Pred(j)]; ar[Pred(j)] := ar[j]; ar[j] := T
end
end;

const
a: arrType = (1, 4, 2, 6, 4, 2, 8, 10, 3, 4);
begin
Bubble(a, n, SortUp); { Для сортировки по возрастанию }
// ...
Bubble(a, n, SortDown); { Для сортировки по убыванию }
end.
volvo
В качестве примера для работы с функциональными (процедурными) типами рассмотрим создание функции для сортировки массива одним из известных методов (подробнее о методах сортировки - смотреть здесь)...

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

Для этого понадобится создать перечисление, содержащее названия реализованных методов сортировки, и каждому из этих названий поставить в соответствие реальную процедуру сортировки этим методом. Перечисление будет иметь вид:
Type
{ это не сами процедуры сортировок, а имена,
по которым можно будет к ним обращаться }
TSortings =
(srBubble, srInsert, srMerge, srHoarFirst,
stHoarSecond, stHeap);


Поставить в соответствие каждому имени реальную процедуру сортировки можно, пользуясь типизированными константами (ведь если существует процедурный тип, и даже процедурные переменные, то могут быть описаны и типизированные константы этого типа). Предположим, что сами процедуры сортировок уже написаны (при этом обратите внимание на замену операции сравнения элементов на функцию для возможности реализации как восходящей, так и нисходящей сортировки одной и той же процедурой), и нужно только организовать соответствие между ними и перечислением имен. Это делается так:
Procedure Bubble...
Procedure Insert...
Procedure Merge...
Procedure HoarFirst...
Procedure HoarSecond...
Procedure HeapSort...
...
Type
TSortProc = Procedure {список формальных параметров};
Const
sortProc: Array[TSortings] Of TSortProc =
(Bubble, Insert, Merge, HoarFirst, HoarSecond, HeapSort);


Теперь при вызове:
sortProc[srBubble]({список параметров для процедуры Bubble})

мы фактически вызываем процедуру:
Bubble({список параметров для процедуры Bubble})

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

При переносе в модуль желательно делать доступными пользователю только те, процедуры (функции) и переменные, которые ему действительно нужны. Так появилась идея "скрыть" от пользователя реализацию функций сравнения элементов массива:
Type
TOrderType =
Function(a, b: Real): Boolean;

Function SortAscend(a, b: Real): Boolean; Far;
Begin SortAscend := (a > b) End;
Function SortDescend(a, b: Real): Boolean; Far;
Begin SortDescend := (a < b) End;

(т.е. перенести их в раздел Implementation), но для того, чтобы он по-прежнему мог выбирать направление сортировки, ввести такое перечисление:
Type
TOrder =
(orAscending, orDescending);

и в разделе Inplementation также воспользоваться массивом функций:
Const
sortOrder: Array[TOrder] Of TOrderType =
(SortAscend, SortDescend);

Таким образом, искомая функция сортировки по требованию пользователя может быть описана так:
Procedure SuperSort(Var arr: arrType; n: Integer;
Style: TSortings; Order: TOrder);

а использовать ее можно следующим образом:
Uses SortUnit;
Const
a: arrType =
(10, 32, 51, 11, 9, 4, 62, 17, 12, 15);
b: arrType =
(110, 132, 151, 111, 19, 14, 162, 117, 112, 115);

begin
SuperSort(a, n, srBubble, orDescending);
{сортировка "A" в убывающем порядке методом "пузырька"}

SuperSort(b, n, srHeapSort, orAscending);
{сортировка "B" в возрастающем порядке методом "пирамиды"}
end.


Реализация модуля, описанного выше содержит одну процедуру:
Procedure SuperSort(Var arr: Array Of TType; n: Integer;
Style: TSortings; Order: TOrder);

где:
arr - сортируемый массив
n - размер сортируемого массива
Style - вид сортировки, который надо применить
может принимать значения:
Цитата
  srBubble - пузырьковая сортировка
  srInsert - сортировка методом "простой вставки"
  srMerge - сортировка слияниями
  srHoarFirst - первый вариант быстрой сортировки Хоара
  srHoarSecond - второй вариант быстрой сортировки Хоара
  srHeap - пирамидальная сортировка

Order - направление сортировки:
Цитата
  orAscending - по возрастанию
  orDescending - по убыванию


Для того, чтобы заставить этот модуль работать с другим встроенным типом данных Паскаля (например, с типом Double) достаточно изменить
Type
TType = Integer;

на
Type
TType = Double;


Присоединенный файл содержит модуль с реализацией данной процедуры
Altair
Процедуры. Вызов одноименных процедур.


Из модуля, который описан позже зарезервированным словом Uses
Можно явно указывать процедуру:


program a1;
uses a2,a3;
procedure Print;
begin
...
end;
...
begin
a1.Print; {будет вызвана процедура, описаная в самой программе}
a3.Print; {будет вызвана процедура, описаная в модуле a3}
a2.Print; {будет вызвана процедура, описаная в модуле a2}
end.



Можно ли сделать процедуру с нефиксированным числом параметров как writeln ?

WriteLn - это не процедура вовсе, это макрос такой, сделанный для
удобства и средствами Pascal не реализуемый.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.