Prozedurale Typen (Delphi)

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Datentypen, Variablen und Konstanten - Index


Prozeduren und Funktionen können als Wert betrachtet und einer Variablen zugewiesen werden, so dass sie sich als Parameter übergeben lassen. Dieses Verfahren wird durch so genannte prozedurale Typen ermöglicht.

Dieses Thema verweist nicht auf die neueren prozeduralen Typen, die mit anonymen Methoden verwendet werden, das heißt, ein "Verweis auf eine Prozedur". Siehe Anonyme Methoden in Delphi.

Allgemeines zu prozeduralen Typen

Nehmen wir an, Sie definieren eine Funktion mit dem Namen Calc, die zwei Integer-Parameter übernimmt und einen Integer-Wert zurückgibt:

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

Sie können die Calc-Funktion nun der Variablen F zuweisen:

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

Wenn Sie im Kopf einer Prozedur oder Funktion den Bezeichner nach dem Wort procedure bzw. function entfernen, erhalten Sie den Namen eines prozeduralen Typs. Dieser Typname kann direkt in einer Variablendeklaration (siehe oben) verwendet oder zur Deklaration neuer Typen benutzt werden:

 
 type
   TIntegerFunction = function: Integer;
   TProcedure = procedure;
   TStrProc = procedure(const S: string);
   TMathFunc = function(X: Double): Double;
 var
   F: TIntegerFunction;   // F ist eine parameterlose Funktion, die einen Integer zurückgibt 
   Proc: TProcedure;      // Proc ist eine parameterlose Prozedur 
   SP: TStrProc;          // SP ist eine Prozedur, die einen String-Parameter übernimmt 
   M: TMathFunc;          // M ist eine Funktion, die einen Double-Parameter (real) 
                          // übernimmt und einen Double-Wert zurückgibt
 
   procedure FuncProc(P: TIntegerFunction);  // FuncProc ist eine Prozedur, 
                          // die als einzigen Parameter eine parameterlose 
                          // Integer-Funktion übernimmt

Methodenzeiger

Alle oben aufgeführten Variablen sind so genannte Prozedurenzeiger, also Zeiger auf die Adresse einer Prozedur oder Funktion. Um eine Methode eines Instanzobjekts zur referenzieren (siehe Klassen und Objekte), muss dem Namen des prozeduralen Typs die Klausel of object hinzugefügt werden. Beispiel:

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

Diese Typen sind Methodenzeiger. Ein Methodenzeiger wird in Form zweier Zeiger codiert, von denen der erste die Adresse der Methode speichert, während der zweite eine Referenz auf das Objekt enthält, zu dem die Methode gehört. Nach den Deklarationen

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

ist die folgende Zuweisung korrekt:

 OnClick := MainForm.ButtonClick;

Zwei prozedurale Typen sind kompatibel, wenn sie folgende Bedingungen erfüllen:

  • Sie verwenden dieselbe Aufrufkonvention.
  • Sie haben denselben (oder keinen) Rückgabetyp.
  • Sie verwenden die gleiche Anzahl Parameter identischen Typs an den entsprechenden Positionen (die Parameternamen sind nicht von Bedeutung).

Unter Win32 sind prozedurale Zeigertypen immer inkompatibel mit Methodenzeigertypen. Der Wert nil kann jedem prozeduralen Typ zugewiesen werden.

Verschachtelte Prozeduren und Funktionen (Routinen, die in anderen Routinen deklariert sind), können nicht als prozedurale Werte verwendet werden. Dasselbe gilt für vordefinierte Prozeduren und Funktionen (Standardroutinen). Wenn Sie eine Standardroutine wie Length als prozeduralen Wert verwenden wollen, müssen Sie die Routine gewissermaßen "verpacken":

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

Prozedurale Typen in Anweisungen und Ausdrücken

Wenn links in einer Zuweisung eine prozedurale Variable steht, erwartet der Compiler auf der rechten Seite einen prozeduralen Wert. Durch die Zuweisung wird aus der Variablen auf der linken Seite ein Zeiger auf die Funktion oder Prozedur auf der rechten Seite. In einem anderen Kontext führt die Verwendung einer prozeduralen Variable zu einem Aufruf der referenzierten Prozedur oder Funktion. Prozedurale Variablen können auch als Parameter übergeben werden:

 var
   F: function(X: Integer): Integer;
   I: Integer;
   function SomeFunction(X: Integer): Integer;
     ...
   F := SomeFunction;    // Zuweisung von SomeFunction an F
   I := F(4);            // Funktionsaufruf; Zuweisung des Ergebnisses an I

In Zuweisungen bestimmt der Typ der Variablen auf der linken Seite, wie die Prozedur oder Methode auf der rechten Seite interpretiert wird. Beispiel:

 var
   F, G: function: Integer;
   I: Integer;
   function SomeFunction: Integer;
     ...
   F := SomeFunction;    // Zuweisung von SomeFunction an F
   G := F;               // Kopieren von F zu G
   I := G;               // Funktionsaufruf; Zuweisung des Ergebnisses an I

Die erste Anweisung weist F einen prozeduralen Wert zu. Die zweite Anweisung kopiert diesen Wert in eine andere Variable. In der dritten Anweisung wird die referenzierte Funktion aufgerufen und das Ergebnis I zugewiesen. Da I keine prozedurale, sondern eine Integer-Variable ist, führt die letzte Zuweisung zum Aufruf der Funktion (die als Ergebnis einen Integer-Wert zurückgibt).

Es gibt Situationen, in denen nicht offensichtlich ist, wie eine prozedurale Variable interpretiert werden muss. Sehen Sie sich dazu folgende Anweisung an:

if F = MyFunction then ...;

In diesem Fall führt F zum Aufruf einer Funktion. Der Compiler ruft zuerst die Funktion auf, die von F referenziert wird, und dann die Funktion MyFunction. Anschließend werden die Ergebnisse verglichen. Hier gilt folgende Regel: Eine prozedurale Variable innerhalb eines Ausdrucks stellt einen Aufruf der referenzierten Prozedur oder Funktion dar. Referenziert F eine Prozedur (die keinen Wert als Ergebnis liefert) oder eine Funktion, an die Parameter übergeben werden müssen, führt die obige Anweisung zu einem Compiler-Fehler. Um den prozeduralen Wert von F mit MyFunction zu vergleichen, verwenden Sie folgende Anweisung:

 if @F = @MyFunction then ...;

@F konvertiert F in eine untypisierte Zeigervariable, die eine Adresse enthält, und @MyFunction liefert die Adresse von MyFunction.

Um anstelle der in einer prozeduralen Variablen gespeicherten Adresse ihre Speicheradresse zu erhalten, verwenden Sie @@. So liefert beispielsweise @@F die Adresse von F.

Der Operator @ kann auch verwendet werden, um einer prozeduralen Variablen einen untypisierten Zeigerwert zuzuweisen. Beispiel:

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

Diese Anweisung ruft die Funktion GetProcAddress auf und weist StrComp das Ergebnis zu.

Jede prozedurale Variable kann den Wert nil enthalten, was bedeutet, dass sie auf keine bestimmte Adresse zeigt. Der Aufruf einer prozeduralen Variablen, die den Wert nil enthält, führt zu einem Fehler. Mit der Standardfunktion Assigned können Sie feststellen, ob einer prozeduralen Variablen ein Wert zugewiesen ist:

 if Assigned(OnClick) then OnClick(X);

Siehe auch