| Процедурные типы и переменные | |
| Процедурные типы | |
| Правила работы с процедурными типами | |
| Процедурные переменные | |
| Приведение типов переменных для процедурных типов | |
| Примеры использования процедурных типов: №1 №2 | |
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;Как видно из приведенных примеров, существует два процедурных типа: тип-процедура и тип-функция.
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].
Правила корректной работы с процедурными типами
x := Integral(0, 1, Sin);Это ограничение, однако, легко обойти, определив свою функцию:
Function MySin(R: Real): Real; Far; Begin MySin := Sin(R) End; ... Begin x := Integral(0, 1, MySin); ... End.
{$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+}, то есть в расчете на дальний вызов)
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.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 и сравнить их значения. Принято, что такое вхождение идентификатора подпрограммы означает вызов функции.
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}
Пример №1 использования процедурных типов
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 ...изменить знак с "больше" на "меньше". Но зачем же мы будем изменять процедуру, тем более, что нам может пригодится сортировка как по возрастанию, так и по убыванию (возможно, даже в пределах одной программы).
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.
Пример №2 использования процедурных типов
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);и в разделе Implementation также воспользоваться массивом функций:
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);
где:Type TType = Integer;на
Type TType = Double;Исходники модуля: sortunit.pas