手続き型(Delphi)
データ型、変数、定数:インデックス への移動
手続き型を使用することによって、手続きおよび関数を、変数に割り当てたり他の手続きや関数に渡すことができる値として扱うことができます。
このトピックでは、無名メソッドで使われる新しい種類の手続き型、つまり "手続きの参照" については扱いません。「Delphi での無名メソッド」を参照してください。
手続き型の概要
ここでは、手続き型の使い方を例で説明します。2 つの整数パラメータを受け取り、整数値を返す 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 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 (real)
// parameter and returns a Double
procedure FuncProc(P: TIntegerFunction); // FuncProc is a procedure
// whose only parameter is a parameterless
// integer-valued function
メソッド ポインタ
Win32 では、前の例で示されたすべての変数は、手続きまたは関数のアドレスへのポインタである手続きポインタです。インスタンス オブジェクトのメソッドを参照する場合(「クラスとオブジェクト(Delphi)」を参照)、of object を手続き型名に追加する必要があります。たとえば、次のようになります。
type
TMethod = procedure of object;
TNotifyEvent = procedure(Sender: TObject) of object;
これらの型は、メソッド ポインタを表しています。メソッド ポインタは、実際は 2 つのポインタです。1 つのポインタはメソッドのアドレスを格納し、もう 1 つのポインタはメソッドが属するオブジェクトへの参照を格納します。次のような宣言が行われているとします。
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
...
end;
var
MainForm: TMainForm;
OnClick: TNotifyEvent
次のように割り当てることができます。
OnClick := MainForm.ButtonClick;
以下の条件を満たす場合、2 つの手続き型には互換性があります。
- 呼び出し規約が同じ
- 戻り値が同じ(または、戻り値なし)
- パラメータの数が同じで、対応する位置に同じ型のパラメータを持つ (パラメータ名は異なってもかまいません)。
手続きポインタ型は、常にメソッド ポインタ型と互換性がありません。値 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; // assign SomeFunction to F
I := F(4); // call function; assign result to I
代入文では、左側の変数の型によって、右側の手続きまたはメソッド ポインタの解釈が決定されます。たとえば、次のようになります。
var
F, G: function: Integer;
I: Integer;
function SomeFunction: Integer;
...
F := SomeFunction; // assign SomeFunction to F
G := F; // copy F to G
I := G; // call function; assign result to I
最初の文は、手続き値を F に割り当てます。2 番目の文は、その値を別の変数にコピーします。3 番目の文は、参照される関数を呼び出し、結果を 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 を保持することができ、それは何もポイントしていないことを意味します。値 nil を持つ手続き変数を呼び出すとエラーになります。手続き変数が割り当てされているかどうかをテストするには、標準関数 Assigned を使用します。
if Assigned(OnClick) then OnClick(X);