Programmierkurs: Delphi: Pascal: Prozedurale Typen
Prozedurale Typen
BearbeitenNein, 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:
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.
Programmhilfe Fehler!
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 Multiplizieren(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.
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
BearbeitenUm 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
:
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:
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!
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.
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: |
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 |
---|
Pascal: Typumwandlung | Inhaltsverzeichnis | Pascal: Rekursion |