Процедурные типы и переменные | |
Процедурные типы | |
Правила работы с процедурными типами | |
Процедурные переменные | |
Приведение типов переменных для процедурных типов | |
Примеры использования процедурных типов: №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