вторник, 10 мая 2011 г.

Процедурные типы

Перевод из справочной системы Delphi
Процедурные типы позволяют вам работать с процедурами и функциями как со значениями, которые могут быть присвоены переменным или переданы другим процедурам и функциям в качестве параметров.

О процедурных типах

Следующий пример демонстрирует использование процедурного типа. Предположим, вы объявили функцию с именем Calc, которая принимает в качестве параметров два целочисленных значения и возвращает одно:

function Calc(X,Y: Integer): Integer;

Вы можете связать функцию Calc с переменной F:

var F: function(X,Y: Integer): Integer;
F := Calc;

Если вы при объявлении процедуры или функции удалите идентификатор, идущий после слов procedure или function, то, что остается слева может быть правой частью объявления процедурного типа. Вы можете использовать такие имена типов непосредственно при объявлении переменных (как в предыдущем примере) или при объявлении новых типов:

type
  TIntegerFunction = function: Integer;
  TProcedure = procedure;
  TStrProc = procedure(const S: string);
  TMathFunc = function(X: Double): Double;
var
  F: TIntegerFunction; // F – это функция без параметров, 
//которая возвращает integer
  Proc: TProcedure;    
// Proc – процедура без параметров 
  SP: TStrProc;        // SP – это процедура, 
//которая принимает один строковый параметр 
  M: TMathFunc;        // M – это функция, 
//которая принимает параметр типа Double (real)
// и возвращает Double

  procedure FuncProc(P: TIntegerFunction);  
// FuncProc – процедура, которая принимает
// один параметр – функцию, которая
// возвращает значение integer

Для платформы Win32 переменные, указанные в предыдущем примере, являются процедурными указателями (указателями, хранящими адрес процедур или функций). Если вы хотите обратиться к методам объекта, вам необходимо добавить в объявление процедуры или функции слова of object. То есть:

type
  TMethod      = procedure of object;
  TNotifyEvent = procedure(Sender: TObject) of object;
  

Эти типы представляют собой указатели на методы. Указатели на методы на самом деле являются парой указателей, первый из которых хранит адрес метода, а второй – ссылку на объект, которому принадлежит метод. Давая объявление вида:

type
  TNotifyEvent = procedure(Sender: TObject) of object;
  TMainForm = class(TForm)
    procedure ButtonClick(Sender: TObject);
     ...
  end;
var
  MainForm: TMainForm;
  OnClick: TNotifyEvent;

Мы можем выполнить следующую инструкцию:

OnClick := MainForm.ButtonClick;

Два процедурных типа совместимы, если они имеют:

  • Одинаковую конвенцию вызова
  • Одинаковое возвращаемое значение (или не имеют такового)
  • Одинаковое количество параметров
  • Параметры на одних и тех же позициях имеют одинаковый тип (имена параметров не имеют значения).

Процедурные указатели всегда несовместимы с указателями на методы объектов. Значение nil может быть присвоено переменной любого процедурного типа.

Вложенные процедуры и функции (подпрограммы, объявленные внутри других подпрограмм) не могут быть использованы в качестве процедурных значений или в качестве предопределенных процедур и функций. Если вы хотите использовать предопределенные подпрограммы (такие как Length) в качестве процедурных значений – используйте "обертку" вида:

function FLength(S: string): Integer;
begin
  Result := Length(S);
end;


Процедурные типы в инструкциях и выражениях

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

var
  F: function(X: Integer): Integer;
  I: Integer;
  function SomeFunction(X: Integer): Integer;
    ...
  F := SomeFunction;    // связывает SomeFunction с F
  I := F(4);            // вызывает функцию; 
//и присваивает результат переменной I

В инструкциях присваивания тип переменной в левой части определяет интерпретацию процедурных указателей или указателей на методы, которые указаны в правой части. Например:

var
  F, G: function: Integer;
  I: Integer;
  function SomeFunction: Integer;
    ...
  F := SomeFunction;      // связывает SomeFunction с F
  G := F;                 // копирует F в G
  I := G;                 // вызывает функцию; 
  
//присваивает результат переменной I

Первая инструкция присваивает процедурное значение переменной F. Вторая инструкция копирует это значение в другую переменную. Третья инструкция выполняет вызов функции, на которую ссылается G и присваивает результат переменной I. Поскольку I – это целочисленная, а не процедурная переменная, последняя инструкция присваивания фактически выполняет вызов функции (которая возвращает целое число).

В некоторых ситуациях не совсем понятно, как должна интерпретироваться процедурная переменная. Рассмотрим инструкцию:

if F = MyFunction then ...;

В этом случае упоминание F приводит к вызову функции. Компилятор вызывает функцию, на которую ссылается F, затем вызывает MyFunction, затем сравнивает результат. Правило следующее: каждый раз, когда процедурная переменная встречается внутри выражения, - она интерпретируется как вызов процедуры или функции. В случае, когда F ссылается на процедуру (которая не может иметь возвращаемого значения), или когда F ссылается на функцию, которая требует параметров, указанная инструкция вызовет ошибку компиляции. Для сравнения процедурных значений, хранящихся в F и MyFunction, следует сравнивать:

if @F = @MyFunction then ...;

@F преобразует F в нетипизированный указатель, содержащий адрес, а @MyFunction возвращает адрес MyFunction.

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

Оператор @ может так же быть использован для связывания нетипизированного указателя с процедурной переменной. То есть:

var StrComp: function(Str1, Str2: PChar): Integer;
   ...
@StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');

вызывает функцию GetProcAddress и указывает StrComp на результат.
Любая процедурная переменная может принимать значение nil, что означает, что она не указывает никуда. При этом попытка вызова подпрограммы по указанию имени этой переменной вызовет ошибку. Для проверки значения процедурной переменной, следует пользоваться стандартной функцией Assigned:

if Assigned(OnClick) then OnClick(X);


1 комментарий: