«Машины должны работать. Люди должны думать»
13. Процедурные типы
Эти типы позволяют трактовать процедуры и функции как значения, которые можно присваивать переменным или передавать в качестве параметров другим подпрограммам. Например, пусть есть описание функции с таким заголовком:
Function Calc(X,Y: Integer): Integer;
Можно присвоить имя функции некоторой переменной F, если она объявлена так:
var
F: Function(X,Y: Integer): Integer;
. . .
F := Calc;
Переменная F имеет процедурный тип. Процедурный тип легко получается из заголовка подпрограммы путем удаления ее имени. Примеры описания процедурных типов:
type
TIntegerFunction = function: Integer;
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
var
F: TIntegerFunction; { F is a parameterless function that returns an integer }
Proc: TProcedure; { Proc is a parameterless procedure }
SP: TStrProc; { SP is a procedure that takes a string parameter }
M: TMathFunc; { M is a function that takes a Double parameter
and returns a Double }
type
Procedure FuncProc(P: TIntegerFunction); { FuncProc is a procedure whose
only parameter is a parameterless integer-valued function }
Все приведенные выше переменные являются указателями, значениями которых являются адреса подпрограмм. Указателю можно присвоить адрес любой подпрограммы, заголовок которой соответствует типу указателя.
Если необходимо использовать процедурную переменную для ссылки на метод объекта, необходимо добавить слова of object к описанию процедурного типа, например:
type
TMethod = procedure of object;
TNotifyEvent = procedure(Sender: TObject) of object;
Эти типы описывают указатели на методы (method pointers) и они отличаются от обычных процедурных типов. Указатель на метод представляет собой, на самом деле, два указателя. Первый из них содержит адрес метода (подпрограммы), а второй – ссылку на объект, которому принадлежит данный метод. Например, если сделать описания
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
. . .
end;
var
MainForm: TMainForm;
OnClick: TNotifyEvent
то будет допустим оператор
OnClick := MainForm.ButtonClick;
Два процедурных типа являются совместимыми, если они имеют:
Ø одинаковый формат вызова (the same calling convention), т.е. оба являются процедурами или функциями;
Ø одинаковый тип возвращаемого значения либо не имеют его вовсе;
Ø одинаковое число, тип и порядок следования параметров. Имена параметров значения не имеют.
Указатель на процедурный тип всегда несовместим с указателем на метод. Процедурный тип может иметь значение nil. Вложенные (nested) процедуры и функции, т.е. описанные внутри других подпрограмм, не могут использоваться как процедурные значения. То же относится и к предопределенным (predefined) в Delphi подпрограммам. Если все же требуется предопределенную подпрограмму использовать как процедурное значение, можно написать свою подпрограмму и вызвать в ней предопределенную, например:
function FictLength(S: string): Integer;
begin
Result := Length(S);
end;
Использование процедурных типов.
Когда переменная процедурного типа указывается в левой части оператора присваивания, в правой части ожидается значение процедурного типа, т.е. имя подпрограммы либо другая процедурная переменная. Во всех других контекстах предполагается вызов подпрограммы, с которой связана процедурная переменная, например:
var
F, G : Function(X : integer):integer;
i : integer;
Function AnyFunction(p : integer):integer;
Begin End;
…
G:= AnyFunction; {вызова функции нет}
F:=G; {вызова функции нет}
i:=F(123); {вызов функции}
В некоторых ситуациях не так очевидно, как интерпретируется употребление процедурных переменных. Возьмем оператор
if F = MyFunction then ...;
В данном контексте компилятор выполнит вызов функций F и MyFunction и сравнит возвращаемые ими значения. Если F или MyFunction являются процедурами или функциями с параметрами, компилятор выдаст ошибку.
Для того чтобы действительно сравнить процедурные значения F и MyFunction, надо писать так:
if @F = @MyFunction then ...;
@F – это нетипизированный указатель, а @MyFunction – адрес функции. returns the address of MyFunction. Для доступа к адресу самой процедурной переменной надо использовать @@F.
Операция @ может быть также использована для того, чтобы присвоить процедурной переменной значение нетипизированного указателя, например:
var StrComp: function(Str1, Str2: PChar): Integer;
. . .
@StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');
В этом примере вызывается функция Windows GetProcAddress, которая возвращает адрес функции сравнения строк.
Любая процедурная переменная может иметь значение nil, для проверки которого надо использовать стандартную функцию Assigned:
if Assigned(OnClick) then OnClick(X);
«12. Тип указатель»
14. Тип Variant