Programmierkurs: Delphi: Pascal: Prozedurale Typen

Aus Wikibooks
Zur Navigation springen Zur Suche springen

Prozedurale Typen[Bearbeiten]

Nein, diese Bezeichnung dient nicht Ihrer Verwirrung! Hierunter versteht man tatsächlich Datentypen, die eine Funktion oder Prozedur darstellen. Dieser Typ gibt dabei vor:

  • ob es sich um eine Prozedur oder Funktion handelt
  • ob und wenn ja, welche Parametertypen diese besitzen muss
  • bei Funktionen, welchen Typ der Rückgabewert haben muss

Zur Laufzeit kann man dann einer Variablen dieses Typs eine entsprechende Prozedur bzw. Funktion zuweisen (und dies natürlich auch mehrfach ändern). Sie verwenden dann diese Variable wie die Funktion selbst. Doch verwirrt? Dazu zwei Beispiele:

Crystal Clear app terminal.png Quelltext:

type
  // nimmt nur parameterlose Prozeduren an
  TProzedur = procedure;

procedure HilfeAnzeigen;
begin
  Writeln('Programmhilfe');
end;

procedure FehlerAusgeben;
begin
  Writeln('Fehler!');
end;

var
  Prozedur: TProzedur;

begin
  Prozedur := HilfeAnzeigen;
  Prozedur;

  Prozedur := FehlerAusgeben;
  Prozedur;
end.

Crystal Clear app kscreensaver.png Ausgabe:

Programmierhilfe
Fehler!


Crystal Clear app terminal.png Quelltext:

type
  // nimmt Funktionen mit 2 Integer-Parametern an, die einen Integer-Wert zurückgeben
  TBerechnung = function(WertA, WertB: Integer): Integer;

function Addieren(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

function Multipizieren(X, Y: Integer): Integer;
begin
  Result := X * Y;
end;

var
  Berechnen: TBerechnung;

begin
  Berechnen := Addieren;
  Writeln(Berechnen(11, 31));

  Berechnen := Multiplizieren;
  Writeln(Berechnen(11, 31));
end.

Crystal Clear app kscreensaver.png Ausgabe:

42
341

Mithilfe von prozeduralen Variablen können Sie sehr dynamische Programmabläufe bewirken und auch unter Umständen Ihren Programmcode reduzieren. Wenn Sie zum Beispiel abhängig vom Wert einer Variablen an mehreren Stellen im Programm eine der verschiedenen Funktionen aufrufen möchten, müssen Sie den Wert dieser Variablen nur einmal prüfen und können einer globalen prozeduralen Variablen die entsprechende Funktion zuweisen. Alle anderen Programmteile verwenden nun einfach diese prozedurale Variable.

Eine weitere Verwendung besteht beim Einbinden von Funktionen aus Bibliotheken (DLL-Dateien), wie Sie in einem späteren Kapitel noch lernen werden.

Mit einem prozeduralen Typ, wie oben definiert, können Sie jedoch nur globale Prozeduren und Funktionen aufnehmen. Auf Methoden von Klassen können Sie damit nicht zurückgreifen, da diese ganz anders im Speicher abgelegt werden.

Prozedurale Typen und Objekte[Bearbeiten]

Um die Methode eines Objekts (also einer instanziierten Klasse) wie im ersten Abschnitt gesehen verwenden zu können, setzen Sie zwischen die Typdefinition und das abschließenden Semikolon noch die Schlüsselwörter of object:

Crystal Clear app terminal.png Quelltext:

type
  TBerechnung = function(WertA, WertB: Integer): Integer of object;

Nun können Sie die Methode einer Klasse sowohl innerhalb als auch außerhalb der Klasse einer Variablen dieses Typs zuweisen und aufrufen. Dies funktioniert selbstverständlich nur so lange, bis der Speicher der ursprünglichen Klasseninstanz freigegeben wurde. Dieses Verfahren wird bei den Ereignissen von Klassen angewandt. Dies sind Eigenschaften von prozeduralem Typ, die einfachsten Ereignisse verwenden den Typ TNotifyEvent, der in der Unit Classes folgendermaßen definiert ist:

Crystal Clear app terminal.png Quelltext:

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

Wie vorhin schon kurz gesagt werden diese prozeduralen Typen im Speicher anders abgelegt als die zuvor behandelten, nämlich als Methodenzeiger. Dies ist ein Record zweier aufeinander folgender Zeiger im Speicher, wobei der erste (Code) auf die Adresse der Methode verweist, während der zweite (Data) die Adresse des Objekts enthält. Das nur, damit Sie einmal davon gehört haben, denn an diese Zeiger kommen Sie ohne Typumwandlungen nicht heran. Sie verwenden auch einen solchen Methodenzeiger wie andere prozedurale Typen.

Als nächstes kommt ein Beispiel, wie Sie eine Methode in einer Klasse als Ereigniseigenschaft einsetzen. Mit einem Ereignis kann die Klasse praktisch Code ausführen, der nicht zur Klasse selbst gehört. Die Klasse kann und muss daher auch nicht wissen, was dieser Code bewirkt. Sie gibt lediglich im Rahmen eines prozeduralen Typs vor, wie diese Methode beschaffen sein muss und kann dabei auch weitere Daten in Form vom Parametern übergeben. Wenn Sie variable Parameter verwenden, kann der andere Programmteil auch das Ergebnis seiner Ereignisbehandlung wieder an die Klasse zurückliefern. Das wird zum Beispiel bei Benutzeroberflächen verwendet, wobei ein Fenster dem Hauptprogramm meldet, dass es sich schließen will. Nur wenn es - vereinfacht ausgedrückt - als Antwort ein Okay zurückgemeldet bekommt, schließt es sich auch wirklich, sonst bleibt es geöffnet. Solche Ereigniseigenschaften beginnen immer mit On (oder, wenn man auf Deutsch programmieren möchte, mit Bei).

Damit das Hauptprogramm weiß, welche Instanz der Klasse das Ereignis ausgelöst hat, sollte man immer wenigstens die Instanz selbst mitliefern, also den Typ TNotifyEvent verwenden.

Da es sich hierbei um einen Methodenzeiger handelt, muss die an die Eigenschaft übergebene Methode selbst eine Funktion einer Klasse sein. Globale Funktionen außerhalb von Klassen funktionieren nicht!

Crystal Clear app terminal.png Quelltext:

uses
  Classes;

{ ===== Typdefinitionen ===== }

type
  TInnenKlasse = class
  private
    FBeiAddition: TNotifyEvent;
  public
    function Addieren(WertA, WertB: Integer): Integer;
    property BeiAddition: TNotifyEvent read FBeiAddition write FBeiAddition;
  end;

  TAussenKlasse = class
  private
    procedure InnenKlasseAddition(Sender: TObject);
  public
    procedure Starten;
  end;

{ ===== Methodendeklarationen ===== }

function TInnenKlasse.Addieren(WertA, WertB: Integer): Integer;
begin
  // Ereignis auslösen, falls eine Methode zugewiesen wurde
  if Assigned(BeiAddition) then
    BeiAddition(Self);

  Result := WertA + WertB;
end;

procedure TAussenKlasse.InnenKlasseAddition(Sender: TObject);
begin
  Writeln('In InnenKlasse findet gleich eine Addition statt!');
end;

procedure TAussenKlasse.Starten;
var
  ik: TInnenKlasse;
begin
  ik := TInnenKlasse.Create;
  ik.BeiAddition := InnenKlasseAddition;
  Writeln('Ergebnis von 2 + 4 = ', ik.Addieren(2, 4));
  ik.Free;
end;

{ ===== Hauptprogrammteil ===== }

var
  ak: TAussenKlasse;

begin
  ak := TAussenKlasse.Create;
  ak.Starten;
  ak.Free;
end.

Crystal Clear app kscreensaver.png Ausgabe:

In InnenKlasse findet gleich eine Addition statt!
Ergebnis von 2 + 4 = 6

Puh! Zugegeben, das ist ziemlich umfangreich. Versuchen Sie einmal in Ruhe, den Ablauf nachzuvollziehen, wenn ak.Starten ausgeführt wird.

Zunächst wird eine lokale Variable vom Typ TInnenKlasse dynamisch erzeugt und dann dessen Ereigniseigenschaft BeiAddition die entsprechende Methode zugewiesen. Hier könnte stattdessen auch ein Feld verwendet werden, das bereits im Konstruktor entsprechend erstellt wird.

Dann wird die Methode Addieren aufgerufen. Diese prüft als erstes, ob eine Ereignisbehandlungsmethode zugewiesen wurde und führt diese dann aus. Die Funktion Assigned ist in Delphi eingebaut und prüft lediglich, ob die Adresse der Eigenschaft nil ist. Da diese Methode wiederum in der äußeren Klasse definiert ist, springt das Programm also kurzzeitig zurück und zeigt den Text "In InnenKlasse findet gleich eine Addition statt!" an.

Anschließend wird erst die Berechnung durchgeführt und das Ergebnis zurückgegeben, das dann - wieder zurück in ak.Starten - angezeigt wird. Falls das zu schnell ging, führen Sie das Programm mit F7 schrittweise aus und beobachten Sie den Ablauf.

Tipp:

Info bulb.png

Das Ereignis kann an jeder Stelle einer Methode ausgelöst werden, üblich ist jedoch meistens am Anfang oder am Ende. Ereignisse, die am Anfang einer Methode auftreten, nehmen oft über einen var-Parameter eine Rückmeldung entgegen, die sie entweder weiter verarbeiten oder die auch zum Abbruch der Methode führen können. Wenn Sie ein Ereignis nicht benötigen, reicht es aus, der entsprechenden Eigenschaft keinen Wert zuzuweisen. Im oberen Beispiel lassen Sie dann einfach die Zeile ik.BeiAddition := InnenKlasseAddition; weg und es wird kein Ereignis ausgelöst.


Arrow left.png Pascal: Typumwandlung Inhaltsverzeichnis Pascal: Rekursion Arrow right.png