home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
sibdemo3.zip
/
DOCE.DAT
/
DOC
/
OOP_REFG.INF
(
.txt
)
next >
Wrap
OS/2 Help File
|
1996-04-14
|
72KB
|
2,647 lines
═══ 1. OOP Referenz fБr SpeedSoft Sibyl Version 2.0 ═══
This help is incomplete in this version ! It is only available in german for
now !
Klassen
Sichtbarkeit
Instanzen
Zuweisungen
Felder
Methoden
Eigenschaften
Metaklassen
Vordefinierte Klassen
Exceptions
Vordefinierte Exceptions
ΓòÉΓòÉΓòÉ 1.1. Klassen ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Klassen
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Klassen?
Die Basis der objektorientierten Programmierung ist der Begriff der Klasse. Ein
Klasse ist ein strukturierter Datentyp, Дhnlich einem Record, jedoch weitaus
mДchtiger.
Ein ReprДsentant einer Klasse wird eine Instanz oder ein Objekt der Klasse
genannt. Objekte sind dynamische Datentypen, die zur Laufzeit erzeugt und
zerstФrt werden. Eine Klasse legt den Bauplan fБr jede ihrer Instanzen fest.
Die Deklaration einer neuen Klasse kann drei Arten von Komponenten aufweisen:
Felder, Methoden und Eigenschaften. ZusДtzlich kФnnen Klassen Бber den
Mechanismus der Vererbung die Komponenten einer bestehenden Klasse Бbernehmen.
Jeder Komponente einer Klasse kann individuell ein Typ von Sichtbarkeit
zugeordnet werden. Dadurch wird festgelegt, wer die Komponente sehen und darauf
zugreifen kann.
Deklaration von Klassen
Klassen werden auf die folgende Weise deklariert:
<Syntaxdiagramm Classes>
Klassen kФnnen nur im Дuсersten GБltigkeitsbereich eines Moduls deklariert
werden. Es ist nicht mФglich, Klassen lokal in Prozeduren oder Funktionen zu
deklarieren. Es ist ebenfalls nicht mФglich, Klassen im Rahmen einer
Variablen-Deklaration einzufБhren.
Es ist in Speed-Pascal Бblich, wenngleich nicht zwingend vorgeschrieben, daс
die Bezeichner von Klassen mit einem 'T' beginnen, um sie als Typbezeichner
kenntlich zu machen.
Komponenten einer Klasse
Die Deklaration einer Klasse kann drei Arten von Komponenten aufweisen: Felder,
Methoden und Eigenschaften.
Felder
sind Variablen innerhalb einer Klasse und werden auf die gleiche Weise
deklariert und benutzt wie die Felder eines Records. Jedes Objekt einer
Klasse verfБgt Бber einen kompletten eigenen Satz aller Felder der
Klasse.
Methoden
sind Prozeduren oder Funktionen, deren FunktionalitДt eng mit der Klasse
verknБpft ist. Methoden werden Бblicherweise in Verbindung mit einer
Instanz aufgerufen. Alle Instanzen einer Klasse teilen sich den gleichen
Satz Methoden, so daс deren Code nur einmal im Programm vorhanden ist.
Eigenschaften
stellen sich dem Benutzer einer Klasse Дhnlich wie Felder dar, bieten
jedoch weitaus mehr MФglichkeiten. Lesende und schreibende Zugriffe auf
eine Eigenschaft kФnnen sowohl auf ein Feld als auch auf eine Methode
umgelenkt werden. Im letzteren Fall wird bei der AusfБhrung einer
Zuweisung implizit die zugrundeliegende Methode aufgerufen. Auсerdem
kФnnen die ZugriffsmФglichkeiten einer Eigenschaft auf nur-Lesen oder
nur-Schreiben eingeschrДnkt werden.
Vererbung
Bei der Deklaration einer neuen Klasse kann eine bereits bestehende Klasse als
Vorfahr angegeben werden, wodurch die neue Klasse automatisch Бber sДmtliche
Komponenten des Vorfahren verfБgt, sofern es deren Sichtbarkeit zulДсt.
ZusДtzlich kФnnen neue Komponenten deklariert oder vom Vorfahren geerbte
Komponenten Бberschrieben werden. Ъberschreiben bedeutet, es wird fБr eine
Komponente der neuen Klasse ein Bezeichner gewДhlt, der bereits im Vorfahren
verwendet wird. Dadurch wird die Бberschriebene Komponente innerhalb der neuen
Klasse verdeckt. Das Ъberschreiben hat aber keinen Einfluс auf den Vorfahren
selbst.
Es kФnnen nur Methoden und Eigenschaften Бberschrieben werden, nicht jedoch
Felder. Beim Ъberschreiben von Methoden ist es wichtig, das Laufzeitverhalten
der verschiedenen Arten von Methoden zu kennen und die geeignete Art
auszuwДhlen.
Achtung: Bei Borland Delphi ist es auch mФglich, Felder des Vorfahren zu
Бberschreiben. Bei Speed-Pascal besteht diese MФglichkeit derzeit nicht.
Die Vererbung ist ein transitive Beziehung. Wenn zum Beispiel eine Klasse C
von einer Klasse B abstammt, die wiederum von einer Klasse A abstammt, dann
erbt B als Nachkomme von A deren Komponenten. C ist Nachkomme von B und damit
auch Nachkomme von A. Zu den Komponenten, die C von B erbt, gehФren also auch
die von A.
Im folgenden Beispiel werden zwei Klassen TWindow und TEditorWindow
deklariert. Da TEditorWindow ein Nachfahre von TWindow ist, besitzt die Klasse
sДmtliche Komponenten von TWindow (FX, FY, FWidth, FHeight und die Methode
Draw) sowie die beiden neuen Felder FCursorRow und FCursorColumn.
type
TWindow = class
FX, FY, FWidth, FHeight: Integer;
procedure Draw;
end;
TEditorWindow = class(TWindow)
FCursorRow, FCursorColumn: Integer;
end;
Durch mehrmalige Anwendung der Vererbung entsteht eine baumartige Hierarchie
von Klassen. Es gibt keine BeschrДnkung fБr die Anzahl der Nachkommen, die
eine Klasse besitzen kann. Jede Klasse hat aber immer genau einen Vorfahren.
Wenn nicht explizit ein Vorfahr angegeben wird, dann nimmt der Compiler die in
der Unit System vordefinierte Klasse TObject als Vorfahren an.
Damit sind die folgenden beiden Klassendeklarationen Дquivalent:
type
TMyObject = class // impliziter Nachkomme von TObject
...
end;
type
TMyObject = class(TObject) // expliziter Nachkomme von TObject
...
end;
Die Hierarchie der Klassen schlДgt sich auch in den Regeln fБr
ZuweisungskompatibilitДt von Objekten nieder, die sich von denen fБr
traditionelle Datentypen unterscheiden.
ΓòÉΓòÉΓòÉ 1.2. Sichtbarkeit ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Sichtbarkeit
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was ist Sichtbarkeit?
Die Sichtbarkeit einer Komponente bestimmt, wer diese Komponente sehen und
darauf zugreifen kann.
Innerhalb des Moduls, in dem eine Klasse deklariert wird, sind immer alle ihre
Komponenten uneingeschrДnkt sichtbar. Die Sichtbarkeit einer Komponente
auсerhalb dieses Moduls kann mit Hilfe der SchlБsselwФrter public, protected
und private abgestuft eingeschrДnkt werden. ZusДtzlich existiert mit published
eine Art von Sichtbarkeit, die Komponenten einer Klasse im Objekt-Inspektor der
Sibyl-Umgebung editierbar macht und spezielle Laufzeitinformationen erzeugt,
die von SPCC-Anwendungen benФtigt werden.
Ein Sichtbarkeitsbezeichner wird in der Deklaration der Klasse vor einer
Komponente eingefБgt. Er gilt ab dieser Stelle entweder fБr alle folgenden
Komponenten der Klasse, oder bis eine andere Art der Sichtbarkeit festgelegt
wird. Die Sichtbarkeit einer Komponente vererbt sich auf die Nachfahren der
Klasse, kann aber in Nachfahren geДndert werden, wenn die Komponente dort
sichtbar ist.
public - Щffentliche Komponenten
Wenn keine andere Art der Sichtbarkeit vorgegeben wird, dann wДhlt der
Compiler automatisch public. Komponenten, die als public gekennzeichnet
werden, sind sowohl innerhalb als auch auсerhalb des Moduls, in dem die
Klassendeklaration beheimatet ist, uneingeschrДnkt sichtbar.
public ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten
gewДhlt wird, die dem Anwender einer Klasse zur VerfБgung stehen sollen.
protected - GeschБtzte Komponenten
protected stellt eine etwas eingeschrДnktere Art von Sichtbarkeit dar als
public. Komponenten, die als protected gekennzeichnet werden, sind
innerhalb des Moduls, in dem ihre Klasse deklariert wird, uneingeschrДnkt
sichtbar. Auсerhalb des Moduls sind sie nur innerhalb von Methoden von
Nachfahren der Klasse sichtbar.
protected ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten
gewДhlt wird, die zwar dem Entwickler von Nachfahren der Klasse zur
VerfБgung stehen, aber vor dem Anwender einer Klasse verborgen werden
sollen.
private - Versteckte Komponenten
private ist die eingeschrДnkteste Art von Sichtbarkeit. Komponenten die
als private gekennzeichnet werden, sind innerhalb des Moduls, in dem ihre
Klasse deklariert wird, uneingeschrДnkt sichtbar. Auсerhalb des Moduls
sind sie nicht sichtbar.
private ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten
gewДhlt wird, die im Rahmen der Kapselung gДnzlich verborgen werden
sollen.
published - VerФffentlichte Komponenten
published stellt eine besondere Art von Sichtbarkeit dar, die innerhalb
der Sibyl-Umgebung zum Einsatz kommt. Komponenten, die als published
gekennzeichnet werden, sind sowohl innerhalb als auch auсerhalb des
Moduls, in dem ihre Klasse deklariert wird, uneingeschrДnkt sichtbar.
Damit verhalten sie sich exakt so wie Komponenten, die als public
gekennzeichnet werden.
ZusДtzlich erzeugt der Compiler aber fБr Felder und Eigenschaften mit der
Sichtbarkeit published spezielle Laufzeitinformationen, die fБr die
Zusammenarbeit mit dem Objekt-Inspektor und fБr das Laufzeitverhalten von
SPCC-Anwendungen von Bedeutung sind.
published ist die Art von Sichtbarkeit, die fБr Komponenten gewДhlt
werden muс, die innerhalb des Objekt-Inspektors editierbar sein sollen.
ZusДtzlich gelten folgende EinschrДnkungen fБr Komponenten mit der
Sichtbarkeit published:
- Handelt es sich bei der Komponente um ein Feld, so muс dieses vom
Typ einer Klasse sein.
- Handelt es sich bei der Komponente um eine Eigenschaft, so muс diese
einfach, also nicht-indiziert sein. Auсerdem ist ihr Typ auf eine
der folgenden MФglichkeiten beschrДnkt:
-- AufzДhlungstyp
-- Ganzzahliger Typ
-- Flieсkommazahl
-- String
-- Mengentyp mit nicht mehr als 16 Elementen
-- Objekttyp
-- Methodenzeiger
ΓòÉΓòÉΓòÉ 1.3. Instanzen ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Instanzen
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Instanzen?
Ein ReprДsentant einer Klasse wird eine Instanz oder ein Objekt der Klasse
genannt. Objekte sind dynamische Datentypen, die zur Laufzeit erzeugt und
zerstФrt werden. Eine Klasse legt den Bauplan fБr jede ihrer Instanzen fest.
Das Erzeugen und ZerstФren von Objekten geschieht mithilfe spezieller Methoden.
Zum Erzeugen eines Objekts wird ein Konstruktor aufgerufen, der den vom Objekt
benФtigten Speicherbereich auf dem Heap reserviert und initialisiert. Wird das
Objekt nicht mehr benФtigt, sollte es durch den Aufruf eines Destruktors
zerstФrt werden, wodurch auch der reservierte Speicher wieder freigegeben wird.
Das Objektmodell von Speed-Pascal basiert auf Referenzen. Eine Variable vom Typ
einer Klasse enthДlt kein Objekt, sondern einen Zeiger. Dieser Zeiger kann zur
Laufzeit ein Objekt referenzieren oder den Wert nil enthalten, um anzudeuten,
daс er gerade kein gБltiges Objekt referenziert. Beim Erzeugen eines neuen
Objekts wird dem Zeiger die Adresse des Speicherbereiches zugewiesen, der fБr
das Objekt reserviert wurde.
Es ist weder notwendig noch mФglich, den Zeiger explizit zu dereferenzieren.
Der Compiler erledigt dies bei Bedarf.
Beim Zugriff auf die Elemente eines Objekts wird der Zeiger automatisch
dereferenziert. Dadurch stellt sich ein Objekt trotz dynamischer
Allokation wie eine statische Variable dar.
Beim Zugriff auf das gesamte Objekt wird der Zeiger nicht dereferenziert.
Stattdessen wird mit dem Wert des Zeigers, also der Adresse der Instanz
gearbeitet. Das wirkt sich insbesondere bei Zuweisungen aus.
Erzeugen von Instanzen
Instanzen werden erzeugt, indem eine spezielle Art von Methode aufgerufen
wird, ein sogenannter Konstruktor. Der Konstruktur reserviert den fБr die
Instanz nФtigen Speicherbereich auf dem Heap und initialisiert das neue
Objekt. Die Adresse der Instanz wird in eine Instanzvariable geschrieben, die
zuvor deklariert sein muс.
Genauere Informationen zur Implementierung und zum Aufruf von Konstruktoren
finden Sie im entsprechenden Abschnitt.
ZerstФren von Instanzen
Objektinstanzen werden zerstФrt, indem eine spezielle Art von Methode, ein
sogenannter Destruktor aufgerufen wird. Der Destruktor fБhrt zuerst Code aus,
der notwendig ist, um die Benutzung er Objektinstanz korrekt zu beenden. Das
schlieсt insbesondere die Freigabe untergeordneter Objekte ein. Dieser Code
wird vom Programmierer festgelegt. Dann gibt der Destruktor den vom Objekt auf
dem Heap belegten Speicherplatz wieder frei.
Genauere Informationen zur Implementierung und zum Aufruf von Destruktoren
finden Sie im entsprechenden Abschnitt.
ΓòÉΓòÉΓòÉ 1.4. Zuweisungen ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Zuweisungen
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was geschieht bei Zuweisungen?
Das Objektmodell von Speed-Pascal basiert auf Referenzen. Eine Variable vom Typ
einer Klasse bietet nicht Platz fБr eine Objektinstanz, sondern nur fБr einen
Zeiger, der eine Objektinstanz referenzieren kann.
Bei einer Zuweisung wird demzufolge keine Kopie des Quellobjektes erstellt.
Stattdessen wird die Adresse des Objektes auf der rechten Seite der Zuweisung
in die Variable auf der linken Seite der Zuweisung geschrieben. Dadurch
referenzieren beide Variablen nach der Zuweisung das gleiche Objekt, was auch
das folgende Beispiel verdeutlicht.
var
A, B: TObject;
begin
A.Create; // Erzeugt eine Instanz von TObject. Die
// Variable A referenziert diese Instanz.
B := A; // Weist B den Wert von A zu. Beide Variablen
// referenzieren nun das gleiche Objekt.
B.Destroy; // ZerstФrt die von B referenzierte Instanz.
// Damit ist auch der Wert von A ungБltig.
end.
ZuweisungskompatibilitДt
Durch den Vererbungsmechanismus ergeben sich verwandtschaftliche Beziehungen
zwischen Klassen. Diesen Beziehungen wird durch erweiterte MФglichkeiten bei
Zuweisungen Rechnung getragen.
Auсer mit sich selbst ist eine Klasse zuweisungskompatibel mit all ihren
Vorfahren. Das schlieсt sowohl den einen direkten als auch jeden weiteren
indirekten Vorfahren ein. Zur Laufzeit kann eine Variable vom Typ einer Klasse
A also eine Instanz genau dieser Klasse A oder eines beliebigen Nachfahren von
A referenzieren.
Umgekehrt gilt diese ZuweisungskompatibilitДt nicht. Eine Klasse ist mit keinem
ihrer Nachfahren zuweisungskompatibel. Zwischen gДnzlich unverwandten Klassen,
die vФllig verschiedenen Оsten der Objekthierarchie entstammen, besteht
ebenfalls keinerlei ZuweisungskompatibilitДt.
Folgendes Beispiel demonstriert eine Reihe von legalen und illegalen
Zuweisungen. Illegale Zuweisungen werden vom Compiler erkannt und nicht
akzeptiert.
type
TVehicle = class(TObject);
TShip = class(TVehicle);
TSubmarine = class(TShip);
TAirplane = class(TVehicle);
var
Vehicle: TVehicle;
Ship: TShip;
Submarine: TSubmarine;
Airplane: TAirplane;
begin
// Die folgenden Zuweisungen sind gБltig.
Vehicle := Ship;
Vehicle := Submarine;
Vehicle := Airplane;
Ship := Submarine;
// Die folgenden Zuweisungen sind ungБltig
// und werden vom Compiler nicht akzeptiert.
Ship := Vehicle; // TShip ist Nachkomme von TVehicle.
Submarine := Vehicle; // TSubmarine ist Nachkomme von TVehicle.
Airplane := Vehicle; // TAirplane ist Nachkomme von TVehicle.
Submarine := Ship; // TSubmarine ist Nachkomme von TShip.
Ship := Airplane; // TShip und TAirplane sind nicht verwandt.
Airplane := Ship; // TAirplane und TShip sind nicht verwandt.
end.
Typwandlungen
Bei Objekten kФnnen - wie bei anderen Datentypen auch - Typwandlungen
durchgefБhrt werden. Durch eine Typwandlung wird die TypprБfung von
Speed-Pascal bewuсt unterlaufen, wodurch sich potentielle Gefahren ergeben. Um
diese Gefahren zu minimieren, bietet Speed-Pascal mit is und as zwei
Operatoren, die unter anderem sichere Typwandlungen ermФglichen.
Der Operator is
Der Operator is dient dazu, zur Laufzeit eine TypprБfung fБr ein Objekt
durchzufБhren. Die Syntax ist:
MyObject is MyClass
Das Ergebnis dieses Ausdrucks ist vom Typ Boolean. Zur Laufzeit wird der
tatsДchliche Typ des Objekts MyObject mit der Klasse MyClass verglichen.
Ist der Typ von MyObject identisch mit MyClass oder einem beliebigen
direkten oder indirekten Nachfahren von MyClass, dann ist das Ergebnis
des Ausdrucks True. In allen anderen FДllen, insbesondere wenn MyObject
nil ist, liefert die Anwendung des Operators den Wert False.
type
TVehicle = class(TObject);
TShip = class(TVehicle);
TAirplane = class(TVehicle);
var
MyVehicle: TVehicle;
begin
...
if MyVehicle is TShip then WriteLn('Es ist ein Schiff.')
else if MyVehicle is TAirplane then WriteLn('Es ist ein Flugzeug.');
...
end.
Beachten Sie, daс MyClass nicht unmittelbar den Namen einer Klasse
angeben muс, sondern auch eine Variable vom Typ einer Metaklasse sein
kann. Die tatsДchliche Klasse wird dann zur Laufzeit dieser Variablen
entnommen. NДheres dazu entnehmen Sie bitte dem Abschnitt Бber
Metaklassen.
Der Operator as
Der Operator as dient dazu, zur Laufzeit sichere Typwandlungen
durchzufБhren. Die Syntax ist:
MyObject as MyClass
Das Ergebnis des Ausdrucks ist vom Typ MyClass. Zur Laufzeit wird anhand
des tatsДchlichen Typs von MyObject geprБft, ob die Typwandlung sicher
ist. Als sicher gilt die Typwandlung genau dann, wenn MyClass identisch
mit dem tatsДchlichen Typ von MyObject oder ein direkter oder indirekter
Vorfahre davon ist. Ist diese Bedingung nicht erfБllt, dann wird eine
Exception ausgelФst.
Sie kФnnen den Operator mit with..do kombinieren, um eine Reihe von
Operationen auf einem Objekt nur dann durchzufБhren, wenn das Objekt
zuweisungskompatibel zu einer bestimmten Klasse ist.
var
MyStream: TStream;
begin
...
with MyStream as THandleStream do // Wenn MyStream zuweisungskompatibel
begin // zu THandleStream ist, dann wird der
WriteLn('Handle: ', Handle); // Block ausgefБhrt. Sonst wird eine
end; // Exception ausgelФst.
...
end;
Beachten Sie, daс MyClass nicht unmittelbar den Namen einer Klasse
angeben muс, sondern auch eine Variable vom Typ einer Metaklasse sein
kann. Die tatsДchliche Klasse wird dann zur Laufzeit dieser Variablen
entnommen. NДheres dazu entnehmen Sie bitte dem Abschnitt Бber
Metaklassen.
Beachten Sie auсerdem, daс die Typwandlung auch dann als sicher gilt,
wenn MyObject den Wert nil enthДlt. Wenn Sie nicht sicher sind, ob
MyObject wirklich ein gБltiges Objekt referenziert, dann sollten Sie dies
zuerst prБfen. Anderenfalls riskieren Sie den Abbruch des Programms mit
einer Schutzverletzung.
ΓòÉΓòÉΓòÉ 1.5. Felder ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûä
Felder
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Felder?
Felder sind Variablen innerhalb einer Klasse.
Sie entsprechen in Deklaration und Anwendung weitgehend den Feldern von
Records. Jedes Objekt verfБgt Бber einen eigenen Satz aller Felder seiner
Klasse, insbesondere auch der Felder, die die Klasse von ihren Vorfahren geerbt
hat.
Das Оndern eines Feldes von Objekt A hat keinen Einfluс auf das gleichnamige
Feld eines Objekts B der gleichen Klasse, solange die Variablen A und B nicht
die gleiche Instanz referenzieren. Hingegen teilen sich alle Objekte einer
Klasse den gleichen Satz Methoden, wodurch deren Code nur einmal vorhanden ist.
Deklaration von Feldern
Felder werden auf die folgende Weise deklariert:
<Syntaxdiagramm: Fields>
Es ist in Speed-Pascal Бblich, wenngleich nicht zwingend vorgeschrieben, daс
Feldbezeichner mit einem F beginnen.
Folgendes Beispiel zeigt die Deklaration von Feldern.
type
TCity = class
FName: string;
FLongitude, FLatitude: Integer;
FPopulation: LongWord;
end;
Alle Instanzen der Klasse TCity verfБgen Бber ihre eigenen Felder zur Aufnahme
von Name, geographischer Position und BevФlkerungszahl einer Stadt.
Benutzen von Feldern
Die Zugriffe auf die Felder eines Objekts entsprechen den Zugriffen auf Felder
von Records. Einzelne Felder kФnnen Бber den Punkt und den Bezeichner des
Feldes selektiert werden. Auсerdem kann eine Operation auf einem Objekt durch
with..do angefБhrt werden, wodurch alle Felder des Objekts direkt ansprechbar
werden.
type
TCompiler = class
FName: string;
FVersionHi, FVersionLo: Integer;
end;
var
MyCompiler: TCompiler;
begin
...
// Qualifizierter Zugriff auf die Felder des Objekts.
MyCompiler.FName := 'Speed-Pascal/2';
MyCompiler.FVersionHi := 2;
MyCompiler.FVersionLo := 0;
// Zugriff auf die Felder mit Hilfe von WITH..DO. Die
// folgende Sequenz hat die gleiche Wirkung wie die
// vorangehende.
with MyCompiler do
begin
FName := 'Speed-Pascal/2';
FVersionHi := 2;
FVersionLo := 0;
end;
// Lesender Zugriff, WITH..DO ohne BEGIN..END.
with MyCompiler do
WriteLn(FName, ' Version ', FVersionHi, '.', FVersionLo);
...
end.
ΓòÉΓòÉΓòÉ 1.6. Methoden ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Methoden
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Methoden?
Methoden sind Prozeduren oder Funktionen, deren FunktionalitДt fest mit einer
Klasse verknБpft ist.
Methoden werden Бblicherweise in Verbindung mit einer Instanz der Klasse
aufgerufen. Bei Klassenmethoden ist auch ein Aufruf in Verbindung mit einer
Klasse mФglich. Felder, Methoden und Eigenschaften einer Klasse sind innerhalb
der Implementierung ihrer Methoden verfБgbar.
Methoden werden an Nachfahren vererbt. Alle Instanzen einer Klasse teilen sich
den gleichen Satz Methoden, so daс deren Code nur einmal im Programm vorhanden
ist.
Es gibt eine Reihe von SchlБsselwФrtern, die das Laufzeitverhalten einer
Methode beeinflussen. WДhrend bei statischen Methoden die Aufrufadresse bereits
zur Zeit der Compilierung feststeht, wird sie bei virtuellen und dynamischen
Methoden erst zur Laufzeit ermittelt. ZusДtzlich existieren
botschaftsverarbeitende Methoden, die das Zusammenspiel verschiedener
Bildschirmelemente mit dem Betriebssystem Бber den Austausch von Nachrichten
koordinieren.
Zwei spezielle Arten von Methoden, Konstruktoren und Destruktoren, dienen zum
Erzeugen und ZerstФren von Objektinstanzen.
Deklarieren von Methoden
Methoden werden auf die folgende Weise deklariert:
<Syntaxdiagramm: Methods>
Folgendes Beispiel deklariert eine Klasse TWindow zur Darstellung eines
Fensters auf dem Bildschirm. Die Klasse enthДlt zwei Methoden zum Lesen und
Schreiben des Fenstertitels.
type
TWindow = class
...
function GetTitle: string;
procedure SetTitle(const S: string);
...
end;
Implementieren von Methoden
Die Methoden einer Klasse werden nach der Deklaration der Klasse implementiert,
Дhnlich der Implementierung einer als forward deklarierten Prozedur oder
Funktion.
Falls die Klasse in einer Unit deklariert wird, findet die Implementierung der
Methoden im implementation-Teil der Unit statt.
Jeder einzelnen Methode geht bei der Implementierung der Name der Klasse
gefolgt von einem Punkt voran.
Die Liste formaler Parameter der Methode kann, muс aber nicht bei der
Implementierung wiederholt werden. Wenn sie wiederholt wird, dann muс sie exakt
mit der Parameterliste der Deklaration Бbereinstimmen.
type
TWindow = class
X, Y, Width, Height: Integer;
Title: string;
...
procedure Draw;
procedure SetPosition(X, Y: Integer);
procedure SetTitle(const S: string);
end;
// Draw besitzt keine Parameter.
procedure TWindow.Draw;
begin
...
end;
// Bei SetPosition wird die gesamte Parameterliste wiederholt.
procedure TWindow.SetPosition(X, Y: Integer);
begin
...
end;
// Bei SetTitle wird die gesamte Parameterliste weggelassen.
procedure TWindow.SetTitle;
begin
...
end;
Der GБltigkeitsbereich aller Komponenten einer Klasse erstreckt sich unter
anderem auf die Implementierung aller Methoden der Klasse. Dadurch sind alle
Felder, Methoden und Eigenschaften der Klasse innerhalb der Methode direkt
zugreifbar.
procedure TWindow.SetTitle(const S: string);
begin
FTitle := S;
Draw;
end;
ZusДtzlich existiert in jeder Methode ein impliziter Parameter Self, der vom
Typ der Klasse ist, zu der die Methode gehФrt. Self referenziert zur Laufzeit
stets das Objekt, auf dem die Methode aufgerufen wurde. Wenn durch eine
with..do-Anweisung oder durch formale Parameter der Methode Komponenten der
Klasse aus dem aktuellen GБltigkeitsbereich verdrДngt werden, dann kann Бber
Self weiterhin auf diese Komponenten zugegriffen werden.
procedure TWindow.SetPosition(X, Y: Integer);
begin
// Der Konflikt zwischen den Feldern X, Y und den
// gleichnamigen formalen Parametern wird durch
// Self gelФst.
Self.X := X;
Self.Y := Y;
Draw;
end;
Falls innerhalb der Implementierung einer Methode einer Klasse auf eine
gleichnamige Methode des Vorfahren zugegriffen werden soll, die in der neuen
Klasse Бberschrieben oder verdeckt wurde, dann ist dies mit dem SchlБsselwort
inherited mФglich.
type
TEditorWindow = class(TWindow)
procedure Draw;
end;
procedure TWindow.Draw;
begin
DrawFrameAndTitle; // Soll den den Rahmen und die Titelzeile zeichnen.
end;
procedure TEditorWindow.Draw;
begin
inherited Draw; // Ruft zuerst TWindow.Draw auf.
DrawContents; // Soll den Inhalt des Editor-Fensters zeichnen.
end;
Aufruf von Methoden
Der Aufruf einer Methode geschieht durch Angabe der Instanz, auf der die
Methode operieren soll, gefolgt von einem Punkt, dem Namen der Methode und der
Liste aktueller Parameter. Innerhalb einer with..do-Anweisung, die eine
Objektinstanz in den aktuellen GБltigkeitsbereich rБckt, kann die Angabe der
Instanz auch entfallen.
type
TWindow = class
procedure SetPosition(X, Y: Integer);
end;
var
MyWindow: TWindow;
begin
...
MyWindow.SetPosition(100, 200); // Qualifizierter Aufruf.
...
with MyWindow do
begin
SetPosition(100, 200); // Aufruf innerhalb von WITH..DO
end; // benФtigt keine Angabe der Instanz.
...
end.
Bei Klassenmethoden besteht zusДtzlich die MФglichkeit, die Methode mit dem
Bezeichner einer Klasse anstelle der Objektinstanz aufzurufen.
Welche Methode zur Laufzeit tatsДchlich aufgerufen, hДngt wesentlich davon ab,
welches Laufzeitverhalten fБr die Methode gewДhlt wurde. WДhrend bei statischen
Methoden die Bindung bereits zur Zeit der Compilierung durchgefБhrt wird,
ermittelt bei virtuellen oder dynamischen Methoden das Laufzeitsystem anhand
des tatsДchlichen Typs der Instanz, welche Methode aufgerufen wird.
Spezielle Arten von Methoden
Statische Methoden
Sofern bei der Deklaration einer Methode keine andere Art der
Methodenzuteilung gewДhlt wird, ist die Methode statisch.
Die Adresse der aufzurufenden Methode steht dann bereits zur Zeit der
Compilierung fest und wird vom Linker fest ins Programm eingetragen.
Virtuelle Methoden
Das Laufzeitverhalten einer Methode wird als virtuell festgelegt, wenn an
die Deklaration der Methode das SchlБsselwort virtual angehДngt wird.
Bei virtuellen Methoden steht die Adresse des Aufrufes nicht bereits zur
Zeit der Compilierung fest. Stattdessen wird diese Entscheidung auf die
Laufzeit des Programms verschoben. Das Laufzeitsystem ermittelt anhand
des tatsДchlichen Typs der Instanz, welche Methode aufgerufen wird.
Wenn eine Methode einmal als virtuell festgelegt wird, dann gilt dies
automatisch auch fБr alle Nachfahren der Klasse, zu der die Methode
gehФrt. Das bedeutet insbesondere, daс die einmal festgelegte Liste
formaler Parameter beim Ъberschreiben der Methode beibehalten werden muс.
Zum Ъberschreiben einer virtuellen Methode wird das SchlБsselwort
override anstelle von virtual eingesetzt.
Folgendes Beispiel zeigt einen typischen Einsatzfall fБr eine virtuelle
Methode. Verschiedene graphische Bildschirmelemente besitzen eine Methode
Draw, mit der sie neu gezeichnet werden. Dadurch, daс diese Methode
virtuell ist, wird zur Laufzeit stets die zur Instanz passende Version
von Draw aufgerufen. Bei einer statischen Methode hingegen wБrde stets
die Version von Draw aufgerufen, die zum deklarierten Typ der Variablen
gehФrt, im Beispiel also TWindow.Draw.
type
TWindow = class(TObject)
procedure Draw; virtual; // Kennzeichnet die Methode als virtuell.
end;
TEditorWindow = class(TWindow)
procedure Draw; override; // Ъberschreibt die virtuelle Methode.
end;
...
var
MyWindow: TWindow;
begin
MyWindow := TWindow.Create; // Erzeugt eine Instanz von TWindow.
MyWindow.Draw; // Ruft TWindow.Draw auf.
MyWindow.Destroy; // ZerstФrt die Instanz.
MyWindow := TEditorWindow.Create; // Erzeugt eine neue Instanz von
// TEditorWindow
MyWindow.Draw; // Ruft TEditorWindow.Draw auf. WДre
// Draw nicht virtuell, wБrde hier
// auch TWindow.Draw aufgerufen, was
// in diesem Fall sicher nicht sehr
// sinnvoll ist.
MyWindow.Destroy; // ZerstФrt die Instanz.
end.
Es ist auch mФglich, eine virtuelle Methode in einem Nachfahren neu zu
deklarieren. Dadurch wird die geerbte virtuelle Methode in der neuen
Klasse verdeckt, analog zu einer statischen Methode, die durch die
Neudeklaration einer statischen Methode gleichen Namens verdeckt wird.
Die neu deklarierte Methode muс dabei wieder eine als virtual
gekennzeichnete virtuelle Methode sein, jedoch kann sich ihre Liste
formaler Parameter von der der verdeckten Methode unterscheiden.
Das Verhalten von auf diese Weise neu deklarierten virtuellen Methoden
ist aber unter UmstДnden schwerer zu durchschauen als das von
Бberschriebenen virtuellen Methoden. Sie sollten deshalb eine einmal als
virtuell gekennzeichnete Methode stets mit override Бberschreiben.
Achtung: Im Gegensatz zu Borland Delphi ist es bei Speed-Pascal derzeit
nicht mФglich, daс eine virtuelle Methode durch die Neudeklaration einer
statischen Methode in einem Nachfahren verdeckt wird. Der Compiler
verbietet dies, um Fehler seitens des Programmierers abzufangen.
Normalerweise handelt es sich dabei eher um ein vergessenes override als
um eine gewollte Neudeklaration. Diese Art von Fehler ist aber meist
schwer zu entdecken, weil das Programm korrekt compiliert wird, und sich
das falsche Verhalten der Methode nur in einzelnen FДllen und oft nur auf
sehr subtile Weise bemerkbar macht.
Virtuelle Methoden werden vom Compiler mit Hilfe einer Tabelle virtueller
Methoden realisiert. Jede Klasse besitzt eine solche Tabelle mit den
Einsprungadressen aller ihrer virtuellen Methoden. Zur Laufzeit wird die
Adresse der aufzurufenden Methode aus dieser Tabelle ermittelt. Der
Aufrufmechanismus ist sehr schnell, benФtigt aber dennoch minimal mehr
Zeit als der Aufruf einer statischen Methode.
Virtuelle Methoden sind ein mДchtiges Hilfsmittel, um Polymorphie zu
erreichen.
Dynamische Methoden
Das Laufzeitverhalten einer Methode wird als dynamisch festgelegt, wenn
an die Deklaration der Methode das SchlБsselwort dynamic angehДngt wird.
Dynamische Methoden verhalten sich exakt so wie virtuelle Methoden,
insbesondere was das Ъberschreiben und die Neudeklaration betrifft.
Achtung: Das SchlБsselwort existiert nur aus GrБnden der KompatibilitДt
zu Borland Delphi. Programme, die auf dynamische Methoden zurБckgreifen,
werden unter Speed-Pascal problemlos funktionieren. Jedoch werden sie
vermutlich geringfБgige Unterschiede in AusfБhrungsgeschwindigkeit und
Speicherbedarf aufweisen.
Abstrakte Methoden
Eine virtuelle oder dynamische Methode wird zu einer abstrakten Methode,
wenn an die Deklaration der Methode das SchlБsselwort abstract angehДngt
wird.
Nur virtuelle und dynamische Methoden kФnnen abstrakt sein, nicht jedoch
statische. Das SchlБsselwort abstract muс dabei dem SchlБsselwort virtual
oder dynamic folgen.
Abstrakte Methoden werden nicht implementiert, kФnnen jedoch von anderen
Methoden oder von Code, der nicht Teil einer Methode ist, benutzt werden.
Ihr Sinn liegt darin, ein funktionsloses Interface bereitzustellen, das
von Nachfahren einer Klasse aufrechterhalten werden muс. Spezialisierte
Nachfahren mБssen abstrakte Methoden mit override Бberschreiben, wenn die
Methode tatsДchlich genutzt werden soll. Der Aufruf einer abstrakten
Methode auf einer Instanz, deren Klasse die Methode nicht Бberschreibt,
fБhrt zu einem Laufzeitfehler.
Innerhalb der Implementierung einer Methode, die eine abstrakte Methode
Бberschreibt, kann nicht mit inherited auf die Methode des Vorfahren
zugegriffen werden.
Folgendes Beispiel deklariert eine Basisklasse TContainer fБr
Container-Objekte. Sie verfБgt Бber zwei abstrakte Methoden zum Vergleich
und zum Vertauschen zweier Elemente anhand ihrer Indizes. Auсerdem
beinhaltet die Klasse eine Sortiermethode. Innerhalb der Sortiermethode
kФnnen die beiden abstrakten Methoden
type
TContainer = class
FSize: Integer;
function BiggerThan(Item1, Item2: Integer): Boolean; virtual; abstract;
procedure Swap(Item1, Item2: Integer); virtual; abstract;
procedure BubbleSort;
end;
procedure TContainer.BubbleSort;
var
N, I: Integer;
begin
for N := FSize downto 2 do
for I := 1 to N - 1 do
if BiggerThan(I, I + 1) then Swap(I, I + 1);
end;
Botschaftsverarbeitende Methoden
Eine Methode wird zu einer botschaftsverarbeitenden Methode, indem an
ihre Deklaration das SchlБsselwort message gefolgt von einer
ganzzahligen, positiven Konstanten angehДngt wird.
ZusДtzlich muс die Methode eine Prozedurmethode sein, die Бber genau
einen var-Parameter beliebigen Typs verfБgt. Auch ein untypisierter
var-Parameter ist erlaubt.
type
TMyButton = class(TButton)
...
procedure WMButton1Down(var Message); message WM_BUTTON1DOWN;
...
end;
Botschaftsverarbeitende Methoden dienen Бblicherweise zum Austausch von
Nachrichten mit dem Betriebssystem, insbesondere bei der Programmierung
von graphischen OberflДchen wie dem OS/2 Presentation Manager oder
Windows. So wird zum Beispiel bei einem Tastendruck oder einem Mausklick
vom Betriebssystem eine Botschaft an das zugehФrige Bildschirmobjekt
gesandt, das diese Botschaft dann verarbeiten muс. Es wird genau die
botschaftsverarbeitende Methode aufgerufen, deren hinter message
angegebene Nummer mit der Identifikation der Nachricht Бbereinstimmt.
Es ist mФglich, weitere Informationen an eine solche Methode zu
Бbergeben. Dazu dient der var-Parameter, der die Ъbergabe fast beliebiger
Typen erlaubt. Wichtig fБr das Funktionieren des Botschaftsmechanismus
ist nur, daс die ersten vier Byte des Parameters die Nummer der Botschaft
enthalten. Die darauf folgenden Daten kФnnen vom Programmierer frei
gewДhlt werden. Damit sehen alle zulДssigen Parameter-Typen etwa wie
folgt aus:
type
TMessageRec = record
MessageID: LongWord; // Die Identifikation der Nachricht.
... // Ab hier beliebige weitere Informationen.
end;
Der OS/2 Presentation Manager und Windows erlauben beim Austausch die
Ъbergabe von zwei zusДtzlichen 32 Bit breiten Parametern, die je nach Art
der Nachricht in mehrere logische 16 oder 8 Bit breite Parameter
unterteilt sein kФnnen. Daher ist fБr Anwendungen, die auf die
SPCC-Bibliothek zurБckgreifen, in der Unit Classes bereits ein
Botschaftstyp TMessage deklariert, der diesen Fall abdeckt.
Es ist mФglich und ausgesprochen sinnvoll, botschaftsverarbeitende
Methoden bei Nachfahren einer Klasse zu Бberschreiben, um ein vom
Vorfahren abweichendes Verhalten bei bestimmten Nachrichten zu
realisieren. Anders als bei virtuellen Methoden ist hier beim
Ъberschreiben das SchlБsselwort override nicht notwendig.
Botschaftsverarbeitende Methoden sind immer dynamisch gebunden; das
heiсt, die Adresse des Aufrufes wird immer zur Laufzeit anhand des
tatsДchlichen Typs der Instanz ermittelt. Dazu legt der Compiler fБr jede
Klasse eine Tabelle von botschaftsverarbeitenden Methoden mit deren
Nummern und Einsprungsadressen an.
Die Informationen dieser Tabelle werden von einer Methode Dispatch
ausgewertet, die von TObject bereitgestellt wird. Da alle Objekte
Nachfahren von TObject sind, steht diese Methode in jedem Objekt zur
VerfБgung.
procedure Dispatch(var Message);
Botschaftsverarbeitende Methoden werden Бblicherweise nicht direkt
aufgerufen, sondern Бber einen Aufruf von Dispatch mit einem geeigneten
Parameter, der die Nummer der Nachricht und mФglicherweise weitere
Informationen enthДlt.
Dispatch erledigt das Auffinden der zur Botschaft passenden Methode und
ruft diese dann auf. Dazu sucht Dispatch sucht zuerst in der Tabelle der
tatsДchlichen Klasse der Instanz nach einer Methode mit der in der
Botschaft enthaltenen Nummer. Ist dort keine solche Methode enthalten,
wird die Tabelle des Vorfahren der Klasse durchsucht, dann die von dessen
Vorfahren, und so weiter.
Es ist wichtig zu wissen, daс fБr den Aufruf einer
botschaftsverarbeitenden Methode Бber Dispatch nur deren Nummer
entscheidend ist. Der Name der Methode ist vФllig irrelevant. Es ist
insbesondere mФglich, eine botschaftsverarbeitende Methode durch eine
Methode vФllig anderen Namens zu Бberschreiben, solange nur die Nummern
Бbereinstimmen.
EnthДlt beim Aufruf von Dispatch der gesamte in Frage kommende Zweig der
Klassenhierarchie keine Methode mit der angegebenen Nummer, dann wird
eine virtuelle Methode DefaultHandler aufgerufen, die von der Basisklasse
TObject wie folgt bereitgestellt wird:
procedure DefaultHandler(var Message); virtual;
Diese Methode tut Бberhaupt nichts. Es ist aber mФglich, DefaultHandler
zu Бberschreiben, um ein anderes Standardverhalten bei unbehandelten
Nachrichten zu implementieren, zum Beispiel die Ausgabe einer
Fehlermeldung.
Die graphischen Bildschirmelemente der SPCC-Bibliothek Бberschreiben
diese Methode derart, daс vom Objekt nicht behandelte Nachrichten an eine
Betriebssystem-Routine Бbergeben werden, die eine passende
Standard-Aktion fБr das entsprechende Bildschirmelement durchfБhrt.
Konstruktoren
Konstruktoren sind spezielle Methoden, deren Aufruf ein neues Objekt
erzeugt und initialisiert.
Konstruktoren werden bei Deklaration und Implementierung durch das
SchlБsselwort constructor eingeleitet. Ein Konstruktor darf eine
beliebige Liste formaler Parameter haben, aber keinen Wert zurБckliefern.
Er hat also die Gestalt einer Prozedurmethode.
Es gibt zwei Varianten, einen Konstruktor aufzurufen. Die eine entspricht
dem Aufruf einer gewФhnlichen Methode auf einer Instanzvariablen, nur daс
diese Variable vor dem Aufruf des Konstruktors noch kein gБltiges Objekt
referenziert. Es wird ein Objekt erzeugt und initialisiert, dessen Typ
dem der Instanzvariablen entspricht. Die Adresse dieses Objekts wird in
die Instanzvariable geschrieben.
type
TCustomer = class(TObject)
private
FName: PString;
public
constructor Create(Name: string);
end;
constructor TCustomer.Create(Name: string);
begin
FName := NewStr(Name);
end;
var
Customer: TCustomer;
begin
// Erzeuge eine Instanz von TCustomer.
Customer.Create('Thomas Jefferson');
end.
Die andere Variante benФtigt die Angabe einer Klasse anstelle der
Instanzvariablen. Das Ergebnis dieses Aufrufes muс im Rahmen einer
Zuweisung an eine Instanzvariable Бbergeben werden. Bei dieser Variante
ist es mФglich, Objekte zu erzeugen, deren Typ nicht exakt dem der
Variablen entspricht, sofern die Regeln fБr ZuweisungskompatibilitДt
eingehalten werden.
type
TMailOrderCustomer = class(TCustomer)
private
FAddress: PString;
public
constructor Create(Name, Address: string);
end;
constructor TMailOrderCustomer.Create(Name, Address: string);
begin
inherited Create(Name);
FAddress := NewStr(Address);
end;
var
Customer1, Customer2: TCustomer;
begin
// Erzeuge eine Instanz von TCustomer.
Customer1 := TCustomer.Create('Thomas Jefferson');
// Erzeuge eine Instanz von TMailOrderCustomer. Da diese Klasse
// zuweisungskompatibel zu TCustomer ist, kann Customer2 diese
// Instanz referenzieren.
Customer2 := TMailOrderCustomer.Create('George Washington',
'White-House, Washington, DC');
end.
Achtung: Bei Borland Delphi ist nur die zweite Variante mФglich. Wenn
Programme entwickelt werden, deren Quellcode zwischen Speed-Pascal und
Delphi ausgetauscht werden soll, dann muс die erste Variante vermieden
werden, was aber keinerlei EinschrДnkung der FunktionalitДt des Programms
darstellt.
Beim Aufruf eines Konstruktors werden automatisch folgende Aktionen
ausgefБhrt:
- Der fБr die Objektinstanz benФtigte Speicher wird auf dem Heap
reserviert. Die Adresse des Speicherbereiches wird in die
Instanzvariable geschrieben, mit der Konstruktor aufgerufen wurde.
- Der reservierte Speicherbereich wird mit Nullen gefБllt. Dadurch
erhДlt jedes Feld des neuen Objekts einen definierten Zustand.
Welcher Zustand dies ist, hДngt vom Typ des Feldes ab:
-- Ganzzahlen und Flieсkommazahlen erhalten den Wert Null.
-- Zeiger und Objektreferenzen erhalten den Wert nil.
-- Einzelne Zeichen erhalten das Nullzeichen Chr(0).
-- Strings erhalten die LДnge Null.
-- Felder vom Typ Boolean erhalten den Wert False.
-- Bei strukturierten Feldern wie Records und Arrays gelten diese
Regeln fБr alle Elemente.
- Der Benutzercode des Konstruktors, also die Folge der Anweisungen
zwischen begin und end, wird ausgefБhrt.
Bei der Implementierung eines Konstruktors sollten die Schritte
ausgefБhrt werden, die nФtig sind, um eine neue Instanz korrekt zu
initialisieren.
Es ist mФglich, mit inherited den Konstruktor des Vorfahren aufzurufen.
Dabei wird nur der Benutzercode des geerbten Konstruktors ausgefБhrt,
nicht aber eine weitere Instanz erzeugt. Es ist Бblich, zuerst den
Konstruktor des Vorfahren aufzurufen, um die von Vorfahren geerbten
Fehler zu initialisieren. Im Anschluс werden meist die zusДtzlichen
Fehler der neuen Klasse initialisiert.
Eine Klasse, die keine Felder zusДtzlich zu denen ihres Vorfahren
deklariert oder deren Felder bei der Initialisierung alle auf Null
gesetzt werden mБssen, kommt eventuell ohne eigenen Konstruktor aus. Der
Konstruktor des Vorfahren, im Extremfall der von TObject, kann diese
Aufgabe Бbernehmen.
Wenn wДhrend des Konstruktoraufrufes an einer beliebigen Stelle eine
Exception ausgelФst wird, dann wird zuerst zum Destruktor verzweigt,
bevor die Exception-Behandlungsroutine angesprungen oder ein
Laufzeitfehler ausgelФst wird. Deshalb ist es notwendig, daс der
Destruktor auf diesen Fall reagieren kann, also auch in der Lage ist, nur
teilweise initialisierte Objekte zu zerstФren.
Konstruktoren kФnnen virtuell sein. Dadurch wird polymorphes Erzeugen von
Objekten mФglich, also von Objekten, deren exakter Typ zur Zeit der
Compilierung noch nicht bekannt ist. Ein virtueller Konstruktor muс immer
in der Zuweisungsvariante aufgerufen werden. Genaueres dazu befindet sich
im Abschnitt Бber Metaklassen.
Destruktoren
Destruktoren sind spezielle Methoden, deren Aufruf ein Objekt zerstФrt.
Destruktoren werden bei Deklaration und Implementierung durch das
SchlБsselwort destructor eingeleitet. Ein Destruktor darf eine beliebige
Liste formaler Parameter haben, was aber Бblicherweise nicht der Fall
ist. Ein Destruktor darf keinen Wert zurБckliefern, hat also die Gestalt
einer Prozedurmethode.
Destruktoren sollten virtuell sein, damit sichergestellt ist, daс zur
Laufzeit der zur Instanz passende Destruktor aufgerufen wird. Jede Klasse
erbt von TObject einen virtuellen, parameterlosen Destruktor Destroy. Es
wird empfohlen, daс Sie diesen Destruktor in Ihren eigenen Klassen mit
override Бberschreiben.
Ein Destruktor wird auf die gleiche Weise aufgerufen wie eine gewФhnliche
Methode. Er fБhrt zuerst den Benutzercode, also die Anweisungsfolge
zwischen begin und end aus, dann gibt er den von der Instanz benФtigten
Speicher auf dem Heap frei.
Es ist mФglich, innerhalb eines Destruktors mit inherited auf den
Destruktor des Vorfahren zuzugreifen. In diesem Fall wird nur der
Benutzercode des geerbten Destruktors ausgefБhrt, die Instanz wird aber
erst beim Beenden des Бbergeordneten Destruktoraufrufes zerstФrt.
type
TCustomer = class(TObject)
private
FName: PString;
public
constructor Create(Name: string);
destructor Destroy; override;
end;
constructor TCustomer.Create(Name: string);
begin
FName := NewStr(Name);
end;
destructor TCustomer.Destroy;
begin
DisposeStr(FName);
end;
var
Customer: TCustomer;
begin
// Erzeuge eine Instanz.
Customer.Create('Thomas Jefferson');
// ZerstФre die Instanz wieder.
Customer.Destroy;
end.
Eine Klasse, die keine Felder zusДtzlich zu denen ihres Vorfahren
deklariert oder deren Felder nur einfache, nicht-verzeigerte Strukturen
enthalten, benФtigt mФglicherweise keine speziellen AufrДumaktionen zum
ZerstФren des Objekts. In diesem Fall kann der Destruktor des Vorfahren
benutzt werden, und es muс kein eigener Destruktor deklariert werden. Ob
dies so ist, mБssen Sie von Fall zu Fall entscheiden.
Wenn wДhrend eines Konstruktoraufrufes eine Exception auftritt, dann wird
zuerst zum Destruktor verzweigt, bevor die Exception-Behandlungsroutine
angesprungen oder ein Laufzeitfehler ausgelФst wird. Deshalb ist es
notwendig, daс ein Destruktor auf diesen Fall reagieren kann, also auch
in der Lage ist, nur teilweise initialisierte Objekte zu zerstФren.
Das ist insbesondere wichtig, wenn das Objekt Бber Felder von Zeiger-
oder Objekttypen verfБgt. Der Destruktor sollte diese Felder auf nil
prБfen, bevor er versucht, die zugehФrigen Strukturen freizugeben.
Das ZerstФren eines Objektes kann sicherer gestaltet werden, wenn statt
des Destruktors die Methode Free aufgerufen wird, die alle Klassen von
TObject erben. Free prБft, ob die Instanz den Wert Nil enthДlt, und ruft
nur dann den Destruktor Destroy auf, wenn das nicht der Fall ist.
Klassenmethoden
Klassenmethoden sind Prozedur- oder Funktionsmethoden, denen bei
Deklaration und Implementierung zusДtzlich das SchlБsselwort class
vorangestellt wird.
Sie kФnnen virtuell sein, dБrfen dann aber wieder nur durch
Klassenmethoden Бberschrieben werden. Die Regeln sind hierbei die
gleichen, die auch bei gewФhnlichen virtuellen Methoden gelten.
type
TConfigFile = class
...
class function GetDefaultName: string;
...
end;
Eine Klassenmethode kann sowohl auf einem Objekt als auch auf einer
Klasse operieren. Damit stehen Klassenmethoden auch dann zur VerfБgung,
wenn keine einzige Instanz einer Klasse existiert.
begin
WriteLn(TConfigFile.GetDefaultName);
end.
Innerhalb der Implementierung einer Klassenmethode darf auсer auf globale
Bezeichner nur auf andere Klassenmethoden zugegriffen werden. Ein Zugriff
auf Felder, Eigenschaften oder gewФhnliche Methoden der Klasse wird vom
Compiler unterbunden. Der auch in Klassenmethoden vorhandene implizite
Parameter Self enthДlt keine Objektreferenz, sondern eine Referenz auf
die Klasse, fБr die die Methode tatsДchlich aufgerufen wurde. Beim Aufruf
der Klassenmethode auf einer Instanz enthДlt Self den tatsДchlichen Typ
dieser Instanz.
Klassenmethoden sind dazu gedacht, bestimmte Prozeduren oder Funktionen
einer Klasse auch dann verfБgbar zu machen, wenn keine Instanz der Klasse
existiert. Dabei handelt es sich Бblicherweise um Methoden, die auf
irgendeine Weise Informationen Бber die Klasse liefern.
TObject vererbt seinen Nachkommen eine Reihe von Klassenmethoden, mit
denen zum Beispiel der Name einer Klasse ermittelt werden kann, der Name
des Moduls, in dem die Klasse deklariert wurde, oder die GrФсe einer
Instanz der Klasse. NДheres dazu entnehmen Sie der Beschreibung der
Klasse TObject.
ΓòÉΓòÉΓòÉ 1.7. Eigenschaften ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Eigenschaften
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Eigenschaften?
Eigenschaften sind benannte Attribute eines Objekts.
Sie stellen sich dem Benutzer Дhnlich wie Felder dar, da sie Бber einen Typ
verfБgen und ihr Wert in Form einer Zuweisung geДndert wird. Die MФglichkeiten
von Eigenschaften gehen aber Бber die von Feldern weit hinaus, da es mФglich
ist, sowohl den lesenden als auch den schreibenden Zugriff auf die Eigenschaft
einzeln zu kontrollieren oder zu verbieten.
Jeder der beiden mФglichen Zugriffsarten einer Eigenschaft liegt entweder ein
Feld oder eine Methode zugrunde. Im ersten Fall wird der Wert der Eigenschaft
aus dem Feld gelesen oder in dieses geschrieben. Im zweiten Fall wird durch das
Lesen oder Schreiben der Eigenschaft implizit die Methode aufgerufen, so daс
durch eine einfache Zuweisung komplexe Operationen in Gang gesetzt werden
kФnnen.
Es ist mФglich, indizierte Eigenschaften zu deklarieren, die fБr den Benutzer
die Gestalt eines Arrays haben. Da beiden Zugriffsarten einer indizierten
Eigenschaft immer eine Methode zugrunde liegt, kann sie im Gegensatz zu
gewФhnlichen Arrays Бber beliebige Typen indiziert werden.
Eine indizierte Eigenschaft kann zur voreingestellten Eigenschaft erklДrt
werden, so daс sich das gesamte Objekt dem Benutzer als Array darstellt. Es ist
in diesem Fall nicht mehr notwendig, bei einem Zugriff auf die Eigenschaft
deren Bezeichner anzugeben.
FБr Eigenschaften, die als published gekennzeichnet werden, erzeugt der
Compiler spezielle Informationen, die zur Laufzeit des Programms oder fБr die
Zusammenarbeit mit dem Objekt-Inspektor der Sibyl-Umgebung von Bedeutung sind.
Das Verhalten einer Eigenschaft in diesem Kontext kann mit einigen speziellen
SchlБsselwФrtern kontrolliert werden.
Deklarieren von Eigenschaften
Eigenschaften werden auf die folgende Weise deklariert:
<Syntaxdiagramm: Properties>
Die Deklaration einer Eigenschaft wird durch das SchlБsselwort property
eingeleitet, gefolgt vom Bezeichner der Eigenschaft und von deren Typ. Es
folgen in beliebiger Reihenfolge die Definitionen dessen, was bei lesendem oder
schreibendem Zugriff auf die Eigenschaft geschieht. Die Definition der mit dem
Lesen der Eigenschaft assoziierten Aktion wird eingeleitet durch das
SchlБsselwort read, die der schreibenden Aktion analog durch write.
Wenn eine der beiden Aktionen nicht definiert wird, dann existiert die
entsprechende Zugriffsart nicht. Der Versuch, den Wert einer nur zum Lesen
freigegebenen Eigenschaft zu Дndern, wird vom Compiler ebenso abgewiesen wie
der Versuch, eine nur zum Schreiben freigegebene Eigenschaft in einem Ausdruck
als Wert einzusetzen und damit implizit auszulesen.
Sowohl das Lesen als auch das Schreiben einer Eigenschaft kann entweder mit
einem Feld oder mit einer Methode assoziiert werden.
Wenn das Lesen mit einem Feld assoziiert ist, dann wird beim Einsetzen der
Eigenschaft in einen Ausdruck der Wert des Feldes herangezogen. Wenn das
Schreiben mit einem Feld assoziiert ist, dann wird beim Zuweisen eines Wertes
an die Eigenschaft dieser Wert in das entsprechende Feld geschrieben. In beiden
FДllen muс das Feld vom gleichen Typ sein wie die Eigenschaft.
Wird das Lesen einer einfachen Eigenschaft mit einer Methode assoziiert, so muс
die Methode eine parameterlose Funktion sein, deren Ergebnistyp dem Typ der
Eigenschaft entspricht. Der Compiler ersetzt jede Benutzung der Eigenschaft in
einem Ausdruck durch einen Aufruf der Methode und bezieht das Funktionsergebnis
dann in den Ausdruck ein.
Wird das Schreiben einer einfachen Eigenschaft mit einer Methode assoziiert, so
muс diese Methode eine Prozedur sein, deren einziger Parameter dem Typ der
Eigenschaft entspricht. Der Compiler ersetzt jede Оnderung des Wertes der
Eigenschaft in Form einer Zuweisung durch einen Aufruf der Methode, wobei er
den neuen Wert der Eigenschaft als aktuellen Parameter der Methode einsetzt.
Die Methoden, die einer Eigenschaft zugrundeliegen, erhalten in Speed-Pascal
Бblicherweise einen Bezeichner, der dem Bezeichner der Eigenschaft entspricht,
wobei der lesenden Methode ein Get und der schreibenden ein Set vorangestellt
wird. Sowohl Felder als auch Methoden, die einer Eigenschaft zugrundeliegen,
sind meist private.
Folgendes Beispiel demonstriert die Arbeit mit einer Eigenschaft, die auf zwei
Methoden basiert.
type
TCustomer = class(TObject)
private
FName: PString;
FID: Integer;
function GetName: string;
procedure SetName(const S: string);
public
constructor Create(Name: string);
destructor Destroy; override;
property ID: Integer // Auf diese Eigenschaft kann nur lesend
read FID; // zugegriffen werden. Sie liefert den
// Wert des Feldes FID.
property Name: string // Auf diese Eigenschaft kann lesend und
read GetName write SetName; // schreibend zugegriffen werden. Sie
// ruft implizit die Methoden GetName
// und SetName auf.
end;
...
function TCustomer.GetName: string;
begin
Result := FName^;
end;
procedure TCustomer.SetName(const S: string);
begin
AssignStr(FName, S);
end;
var
Customer: TCustomer;
begin
...
with Customer do
begin
WriteLn('ID: ', ID); // Liest implizit das Feld FID aus.
WriteLn('Name: ', Name); // Ruft implizit die Funktion GetName auf.
end;
...
end.
Zugriff auf Eigenschaften
Der Zugriff auf Eigenschaften eines Objekts entspricht aus der Sicht des
Benutzers dem Zugriff auf Felder.
Der Wert der Eigenschaft wird gelesen, indem die Eigenschaft in AusdrБcken
eingesetzt wird. Eine Eigenschaft erhДlt einen neuen Wert durch eine Zuweisung.
Falls einer der beiden Zugriffsarten eine Methode zugrunde liegt, wird diese an
den entsprechenden Stellen des Programms automatisch aufgerufen.
Da Eigenschaften nicht notwendigerweise tatsДchlich vorhandene Felder
zugrundeliegen, kann der Operator @ nicht benutzt werden, um die Adresse einer
Eigenschaft zu ermitteln. Ebenso ist es nicht mФglich, eine Eigenschaft als
aktuellen Parameter an eine Prozedur oder Funktion zu Бbergeben, die dort einen
var-Parameter erwartet. Der Compiler fДngt diese Fehler ab.
Indizierte Eigenschaften
Eine indizierte Eigenschaft stellt sich dem Benutzer wie ein Array dar.
Bei der Deklaration einer indizierten Eigenschaft wird zwischen ihrem
Bezeichner und ihrem Typ in eckigen Klammern eine beliebige Liste von formalen
Parametern angegeben, Бber die die Eigenschaft indiziert wird. Dabei ist jeder
Typ erlaubt, der in der formalen Parameterliste einer Prozedur erlaubt ist;
indizierte Eigenschaften sind nicht wie Arrays auf AufzДhlungstypen beschrДnkt.
Die mit dem Lesen und Schreiben einer indizierten Eigenschaft assoziierten
Aktionen mБssen Methoden sein. Sie werden auf die gleiche Weise angegeben wie
bei einfachen Eigenschaften, aber sie mБssen besondere Bedingungen erfБllen:
Die mit dem Lesen der Eigenschaft assoziierte Methode muс eine Funktion
sein. Ihre formale Parameterliste muс der in eckigen Klammern angegebenen
Parameterliste der Eigenschaft entsprechen. Der Typ des
Funktionsergebnisses muс dem Typ der Eigenschaft entsprechen.
Die mit dem Schreiben der Eigenschaft assoziierte Methode muс eine
Prozedur sein. Ihre formale Parameterliste muс der in eckigen Klammern
angegebenen Parameterliste der Eigenschaft entsprechen. ZusДtzlich muс
sie einen weiteren Parameter am Ende ihrer Parameterliste besitzen,
dessen Typ mit dem Typ der Eigenschaft Бbereinstimmt.
type
TStrings = class(TObject)
protecedD
function GetValue(const Name: string): string; virtual;
procedure SetValue(const Name, Value: string); virtual;
...
public
property Values[const Name: string]: string
read GetValue write SetValue;
end;
...
var
MyStrings: TStrings;
begin
...
MyStrings.Values['Compiler'] := 'Speed-Pascal/2 Version 2.0';
WriteLn(MyStrings.Values['Compiler']);
...
end.
Indizierte Eigenschaften dБrfen nicht als published gekennzeichnet werden,
weil der Compiler fБr sie keine Laufzeitinformationen erzeugen kann.
Voreingestellte Eigenschaften
Es ist mФglich, eine indizierte Eigenschaft zur voreingestellten Eigenschaft
der Klasse zu erklДren. Dies geschieht durch AnhДngen des SchlБsselwortes
default an die Deklaration der Eigenschaft.
Auf eine voreingestellte Eigenschaft kann zugegriffen werden, ohne daс der
Bezeichner der Eigenschaft angegeben werden muс. Der gewБnschte Index der
Eigenschaft folgt in eckigen Klammern und ohne Punkt unmittelbar auf den
Bezeichner der Instanzvariablen. Damit stellt sich dem Benutzer das gesamte
Objekt wie ein Array dar. Er kann jedoch weiterhin auf alle Komponenten des
Objektes Бber deren Bezeichner zugreifen.
type
TStrings = class(TObject)
protected
function Get(Index: LongInt): string; virtual; abstract;
procedure Put(Index: LongInt; const S: string); virtual; abstract;
...
public
property Strings[Index: LongInt]: string read Get write Put; default;
...
end;
...
var
MyStrings: TStrings;
begin
...
MyStrings[1] := 'Speed-Pascal/2 Version 2.0';
WriteLn(MyStrings[1]);
...
end.
Eine Klasse kann nur eine voreingestellte Eigenschaft besitzen. Die
Voreinstellung einer Eigenschaft vererbt sich an Nachfahren, so daс die
Eigenschaft auch dort voreingestellt ist und bleibt. Auch die Sichtbarkeit
einer voreingestellten Eigenschaft kann in Nachfahren nicht geДndert werden.
Beachten Sie, daс der Angabe von default zum Festlegen einer voreingestellten
Eigenschaft ein Semikolon vorangeht, im Gegensatz zum Festlegen des
Standardwertes einer Eigenschaft im Objekt-Inspektor, der ebenfalls mit dem
SchlБsselwort default, aber ohne Semikolon angegeben wird.
Gemeinsame Zugriffsmethoden
Das SchlБsselwort index wird derzeit nicht unterstБtzt.
Eigenschaften und Laufzeitinformationen
Die SchlБsselwФrter default, nodefault und stored werden derzeit nicht
unterstБtzt.
ΓòÉΓòÉΓòÉ 1.8. Metaklassen ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Metaklassen
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Metaklassen?
Eine Metaklasse ist ein Datentyp, dessen Werte Klassen sind.
Оhnlich wie eine Klasse einen Typ definiert, der Objektreferenzen aufnehmen
kann, kann eine Variable vom Typ einer Metaklasse Referenzen auf Klassen
enthalten. Deshalb werden Metaklassen auch als Klassenreferenztypen bezeichnet.
Der Wertebereich einer Metaklasse umfaсt die Klasse, fБr die sie deklariert
wurde, und alle deren Nachfahren, insbesondere auch jene, die erst zu einem
spДteren Zeitpunkt deklariert werden. Einer Variablen vom Typ einer Metaklasse
kФnnen also genau die Klassen als Werte zugewiesen werden, die mit der Klasse
zuweisungskompatibel sind, fБr die die Metaklasse deklariert wurde.
Metaklassen kФnnen Бberall dort im Programm eingesetzt werden, wo auch direkt
mit Klassen operiert wird. Das betrifft insbesondere den Aufruf von
Klassenmethoden, das PrБfen und Wandeln von Typen mit den Operatoren is und as,
sowie das polymorphe Konstruieren von Objekten. In allen FДllen muс die
tatsДchliche Klasse nicht bereits zur Zeit der Compilierung feststehen, sondern
es wird die Klasse benutzt, die zur Laufzeit als Wert der Variablen vorgefunden
wird.
Deklarieren von Metaklassen
Metaklassen werden auf die folgende Weise deklariert:
<Syntaxdiagramm: Metaclasses>
Eine Metaklasse wird mit den SchlБsselwФrtern class of deklariert. Die Klasse,
zu der die Metaklasse gehФrt, muс bereits deklariert sein, zumindestens mit
einer forward-Deklaration.
type
TVehicle = class
...
end;
TVehicleClass = class of TVehicle;
Die Unit System deklariert bereits eine Metaklasse TClass als class of TObject.
Benutzen von Metaklassen
Variablen vom Typ einer Metaklasse kФnnen als Werte alle Klassen aufnehmen, die
mit der Klasse, fБr die die Metaklasse deklariert wurde, zuweisungskompatibel
sind. ZusДtzlich kann einer solchen Variablen der Wert nil zugewiesen werden,
um anzudeuten, daс die Variable momentan keine gБltige Klasse referenziert.
type
TVehicle = class(TObject);
...
end;
TShip = class(TVehicle)
...
end;
TVehicleClass = class of TVehicle;
var
AllowedVehicles: TVehicleClass;
begin
...
AllowedVehicles := TVehicle;
...
AllowedVehicles := TShip;
...
end.
Variablen vom Typ einer Metaklasse kФnnen Бberall dort im Programm eingesetzt
werden, wo auch direkt mit Klassen operiert wird. Insbesondere kФnnen sie auf
der rechten Seite eines der Operatoren is und as auftauchen, bei Aufruf von
Klassenmethoden und beim Konstruieren von Objekten.
Metaklassen und Klassenoperatoren
Metaklassen kФnnen auf der rechten Seite eines Ausdrucks benutzt werden,
der mit den Operatoren is und as gebildet wird. Es gelten dabei
prinzipiell die gleichen Regeln wie bei der direkten Ang
Metaklassen und Klassenmethoden
Metaklassen kФnnen benutzt werden, um eine Klassenmethode aufzurufen. Der
entsprechende Variablenbezeichner wird dabei gefolgt von einem Punkt vor
den Methodenbezeichner gesetzt, analog zum Aufruf der Methode Бber einen
Klassenbezeichner. Der tatsДchliche Typ der Klasse steht dann nicht
bereits zur Zeit der Compilierung fest, sondern wird erst zur Laufzeit
der Variablen entnommen. Es wird die Klassenmethode aufgerufen, die dem
tatsДchlichen Wert der Klassenreferenz entsricht.
Achten Sie darauf, daс Klassenmethoden, die Sie auf diese Weise benutzen
wollen, als virtuell gekennzeichnet sind. Ansonsten findet die Bindung
wie gewohnt bereits zur Zeit der Compilierung statt. Es wird dann stets
die Methode der Klasse aufgerufen, zu der die Metaklasse der Variablen
deklariert wurde.
Metaklassen und Konstruktoren
Metaklassen kФnnen beim Aufruf eines Konstruktors zum Erzeugen eines
neuen Objekts benutzt werden. Bedingung dafБr ist, daс die
Zuweisungsvariante des Konstruktoraufrufes gewДhlt wird, da die einfache
Variante nur die Angabe einer Instanzvariablen, nicht aber einer Klasse
erlaubt.
Diese Art des Aufrufes von Konstruktoren ermФglicht das polymorphe
Konstruieren von Objekten, also von Objekten, deren tatsДchlicher Typ
nicht bereits zur Zeit der Compilierung feststeht. Die Klasse, von der
die neue Instanz erzeugt wird, wird zur Laufzeit der Variablen entnommen.
Achten Sie darauf, daс Konstruktoren, die Sie zum polymorphen
Konstruieren von Objekten benutzen wollen, als virtuell gekennzeichnet
sind. Ansonsten findet die Bindung wie gewohnt bereits zur Zeit der
Compilierung statt. Es wird dann stets eine Instanz der Klasse erzeugt,
zu der die Metaklasse der Variablen deklariert wurde.
type
TVehicle = class
constructor Create; virtual;
end;
TShip = class(TVehicle)
constructor Create; override;
end;
TVehicleClass = class of TVehicle;
var
VehicleType: TVehicleClass;
MyVehicle: TVehicle;
begin
...
VehicleType := TShip;
MyVehicle := TVehicleType.Create;
...
end.
ΓòÉΓòÉΓòÉ 1.9. Vordefinierte Klassen ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Vordefinierte Klassen
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
TObject und TClass
Die in der Unit System deklarierte Klasse TObject ist die Basis der
Objekthierarchie von Speed-Pascal. TObject besitzt rudimentДre Konstruktoren
und Destruktoren sowie eine Reihe von Methoden, die fБr die Zusammenarbeit von
Objekten mit der Sibyl-Umgebung wichtig sind.
TObject ist letztlich Vorfahr jeder anderen Klasse. Wenn bei der Deklaration
einer neuen Klasse nicht explizit ein Vorfahr angegeben wird, dann nimmt der
Compiler TObject als Vorfahren an. Damit ist sichergestellt, daс alle Objekte
auf eine gemeinsame Basis zurБckgreifen kФnnen.
TClass ist die Metaklasse von TObject. Da alle anderen Klassen aufgrund ihrer
Abstammung zuweisungskompatibel zu TObject sind, kann eine Variable vom Typ
TClass als Werte sДmtliche Klassen aufnehmen, die jemals mit Speed-Pascal
deklariert werden.
Deklaration von TObject und TClass
Die Klasse TObject ist zusammen mit ihrer Metaklasse TClass in der Unit System
auf die folgende Weise deklariert:
type
TObject = CLASS;
TClass = CLASS OF TObject;
TObject = CLASS
CONSTRUCTOR Create;
DESTRUCTOR Destroy; VIRTUAL;
PROCEDURE Free;
CLASS FUNCTION NewInstance: TObject; VIRTUAL;
PROCEDURE FreeInstance; VIRTUAL;
CLASS FUNCTION InitInstance(Instance: Pointer): TObject;
CLASS FUNCTION ClassType: TClass;
CLASS FUNCTION ClassName: STRING;
CLASS FUNCTION ClassUnit: STRING;
CLASS FUNCTION ClassParent: TClass;
CLASS FUNCTION GetClassInfo: POINTER;
CLASS FUNCTION InstanceSize: LONGWORD;
CLASS FUNCTION InheritsFrom(AClass: TClass): BOOLEAN;
PROCEDURE DefaultHandler(VAR Message); VIRTUAL;
PROCEDURE DefaultFrameHandler(VAR Message); VIRTUAL;
PROCEDURE Dispatch(VAR Message);
PROCEDURE DispatchCommand(VAR Message;Command:LONGWORD);
PROCEDURE FrameDispatch(VAR Message);
CLASS FUNCTION MethodAddress(CONST Name: STRING): POINTER;
CLASS FUNCTION MethodName(Address: POINTER): STRING;
FUNCTION FieldAddress(Name: STRING): POINTER;
END;
Methoden von TObject
Viele Methoden von TObject werden entweder intern vom Laufzeitsystem benФtigt
oder dienen zum Austausch von Informationen mit der visuellen
Entwicklungsumgebung Sybil und der SPCC-Bibliothek. Eine Reihe von Methoden,
unter anderem Konstruktor, Destruktor und die Methoden zur
Botschaftsverarbeitung, kФnnen und sollen vom Benutzer verwendet werden.
CONSTRUCTOR Create;
Der Konstruktor Create erzeugt eine neue, leere Instanz von TObject und
fБllt sie durch einen Aufruf von InitInstance mit Nullbytes.
In Nachfahren von TObject mБssen Sie den Konstruktor mit hoher
Wahrscheinlichkeit Бberschreiben, um zusДtzliche Felder des Objektes
korrekt zu initialisieren. Wenn bei der Initialisierung alle Felder auf
Null gesetzt werden, oder die neue Klasse keine Felder besitzt, dann kann
auch der Konstruktor von TObject diese Aufgabe Бbernehmen.
DESTRUCTOR Destroy; VIRTUAL;
Der Destruktor Destroy zerstФrt die Instanz und gibt den von ihr belegten
Speicher auf dem Heap frei.
Wenn in einem Nachfahren von TObject keine weiteren Aktionen zum
ZerstФren einer Instanz notwendig sind, dann kann der Destruktor von
TObject auch als Destruktor fБr diese Klasse benutzt werden. Wenn die
Klasse komplexe Unterstrukturen besitzt, die aufgerДumt werden mБssen,
dann benФtigt die Klasse einen eigenen Destruktor.
Es wird empfohlen, daс jede Klasse, die einen Destruktor benФtigt,
Destroy prinzipiell als parameterlosen Destruktor beibehДlt und mittels
override Бberschreibt.
PROCEDURE Free;
Die Methode Free prБft eine Instanz auf nil und ruft genau dann den
Destruktor Destroy auf, wenn der Wert der Instanz nicht nil ist, die
Variable also ein gБltiges Objekt referenziert.
Benutzen Sie Free, um mehr Sicherheit beim ZerstФren eines Objektes zu
erhalten. Ein direkter Aufruf eines Destruktors auf einer
Instanzvariablen, die nil enthДlt, bricht das Programm mit einem
Laufzeitfehler ab. Die Verwendung von Free verhindert diesen Fehler.
CLASS FUNCTION NewInstance: TObject; VIRTUAL;
Diese Methode wird derzeit nicht verwendet.
PROCEDURE FreeInstance; VIRTUAL;
Diese Methode wird derzeit nicht verwendet.
CLASS FUNCTION InitInstance(Instance: Pointer): TObject;
Die Klassenmethode InitInstance fБllt die Бbergebene Objektinstanz mit
Nullbytes, wodurch alle ihre Felder zurБckgesetzt werden. Die Methode
wird beim Erzeugen eines neuen Objektes vom Konstruktor Create
aufgerufen.
CLASS FUNCTION ClassType: TClass;
Die Klassenmethode ClassType liefert tatsДchlichen Typ einer Klasse oder
einer Klassenreferenz. Wird die Methode auf einem Objekt aufgerufen, dann
ist dies die tatsДchliche Klasse des Objekts zur Laufzeit.
CLASS FUNCTION ClassName: STRING;
Die Klassenmethode ClassName liefert den Namen einer Klasse. Wird die
Methode auf einem Objekt aufgerufen, dann ist dies der Name der
tatsДchlichen Klasse des Objekts zur Laufzeit.
Der Name wird komplett in Groсbuchstaben zurБckgegeben.
CLASS FUNCTION ClassUnit: STRING;
Die Klassenmethode ClassUnit liefert den Namen des Moduls, in dem eine
Klasse deklariert wurde.
CLASS FUNCTION ClassParent: TClass;
Die Klassenmethode ClassParent liefert den tatsДchlichen Typ des
unmittelbaren Vorfahren einer Klasse oder einer Klassenreferenz zurБck.
Wird die Methode auf einem Objekt aufgerufen, dann ist dies der Vorfahre
der tatsДchlichen Klasse des Objekts.
CLASS FUNCTION GetClassInfo: POINTER;
Die Klassenmethode GetClassInfo liefert einen Zeiger auf interne
Informationen Бber die Klasse.
Achtung: Bei Borland Delphi heiсt diese Klassenmethode ClassInfo. Sie
wurde in Speed-Pascal umbenannt, da das OS/2-API bereits eine Funktion
mit dem gleichen Namen bereitstellt.
CLASS FUNCTION InstanceSize: LONGWORD;
Die Klassenmethode InstanceSize liefert die GrФсe einer Instanz einer
Klasse.
CLASS FUNCTION InheritsFrom(AClass: TClass): BOOLEAN;
Die Klassenmethode InheritsFrom prБft, ob die Klasse, auf der die Methode
aufgerufen wird, ein direkter oder indirekter Nachfahre von AClass ist.
Sie gibt entsprechend True oder False zurБck.
PROCEDURE Dispatch(VAR Message);
Die Methode Dispatch dient zum Verteilen von Nachrichten an
botschaftsverarbeitende Methoden.
Dispatch sucht die zur Identifikation der Nachricht passende Methode
heraus und ruft diese auf.
Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem
beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann
wird die Methode DefaultHandler aufgerufen, die eine Standardbehandlung
der Nachricht durchfБhrt.
PROCEDURE DispatchCommand(VAR Message;Command
Die Methode DispatchCommand dient zum Verteilen von Nachrichten des Typs
WM_COMMAND an botschaftsverarbeitende Methoden.
DispatchCommand sucht die zur Identifikation der Nachricht passende
Methode heraus und ruft diese auf.
Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem
beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann
wird die Methode DefaultHandler aufgerufen, die eine Standardbehandlung
der Nachricht durchfБhrt.
PROCEDURE FrameDispatch(VAR Message);
Die Methode FrameDispatch dient zum Verteilen von Nachrichten an
botschaftsverarbeitende Methoden. Im Gegensatz zu Dispatch wird
FrameDispatch fБr Nachrichten benutzt, die an das Rahmenfenster eines
Bildschirmobjektes gehen sollen.
FrameDispatch fБr sucht die zur Identifikation der Nachricht passende
Methode heraus und ruft diese auf.
Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem
beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann
wird die Methode DefaultFrameHandler aufgerufen, die eine
Standardbehandlung der Nachricht durchfБhrt.
PROCEDURE DefaultHandler(VAR Message); VIRTUAL;
Die Methode DefaultHandler wird von Dispatch aufgerufen, wenn keine
Methode zur Behandlung einer bestimmten Botschaft gefunden werden konnte.
DefaultHandler tut nichts, wird aber von graphischen Bildschirmelementen
derart Бberschrieben, daс eine Routine des Betriebssystems aufgerufen
wird, die eine Standardbehandlung der Nachricht durchfБhrt.
PROCEDURE DefaultFrameHandler(VAR Message); VIRTUAL;
Die Methode DefaultFrameHandler wird von FrameDispatch aufgerufen, wenn
keine Methode zur Behandlung einer bestimmten Botschaft gefunden werden
konnte. DefaultFrameHandler tut nichts, wird aber von graphischen
Bildschirmelementen derart Бberschrieben, daс eine Routine des
Betriebssystems aufgerufen wird, die eine Standardbehandlung der
Nachricht durchfБhrt.
CLASS FUNCTION MethodAddress(CONST Name: STRING): POINTER;
Die Klassenmethode MethodAddress ermittelt die Einsprungadresse einer
Methode anhand von deren Namen.
CLASS FUNCTION MethodName(Address: POINTER): STRING;
Die Klassenmethode MethodName ermittelt den Namen einer Methode anhand
ihrer Einsprungadresse.
FUNCTION FieldAddress(Name: STRING): POINTER;
Die Methode FieldAddress liefert die Adresse eines Feldes anhand von
dessen Namen.
ΓòÉΓòÉΓòÉ 1.10. Exceptions ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Exceptions
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Was sind Exceptions?
Exceptions sind ein Hilfsmittel zur Behandlung von Ausnahmesituationen in
Programmen, insbesondere zur Fehlerbehandlung.
Wenn eine Exception ausgelФst wird, verzweigt das Programm sofort zu einem
Exception-Handler, der auf den Fehler reagieren kann. Existiert kein solcher
Exception-Handler dann wird das Programm mit einer Fehlermeldung abgebrochen.
Durch die Benutzung von Exceptions wird der normale Kontrollfluс des Programms
vom Kontrollfluс fБr FehlerfДlle getrennt, wodurch das Programm besser
strukturiert und leichter zu warten wird.
Exceptions sind Objekte. Neue Exceptions kФnnen von der Vererbung Gebrauch
machen, wodurch sich eine baumartige Hierarchie von Exceptions ergibt. Von
dieser Struktur profitieren Exception-Handler, indem sie viele spezielle
Exceptions, die einen gemeinsamen Vorfahren besitzen, gleichzeitig behandeln.
Als Objekte kФnnen Exceptions auсerdem beliebige weitere Komponenten besitzen,
zum Beispiel Felder fБr zusДtzliche Informationen, die beim AuslФsen der
Exception gefБllt werden. Auch von diesen Informationen kann der
Exception-Handler Gebrauch machen, etwa indem eine detaillierte Fehlermeldung
angezeigt wird.
Deklarieren von Exceptions
Die Deklaration einer Exception entspricht der Deklaration einer Klasse, da
Exceptions Objekte sind. Exceptions sollten, mБssen aber nicht unbedingt von
der vordefinierten Klasse Exception oder einer ihrer Nachfahren abstammen.
In sehr vielen FДllen ist nur die Art des aufgetretenen Fehlers relevant. Diese
findet sich dank der zur Laufzeit verfБgbaren Typinformationen bereits in der
Klasse der Exception wieder. Deshalb werden Exceptions meist sehr einfach
deklariert:
type
EMyException = class(Exception);
Es ist in Speed-Pascal Бblich, die Bezeichner von Exceptions stets mit dem
Buchstaben 'E' zu beginnen.
Genau wie gewФhnliche Klassen kФnnen auch Exceptions durch geschickte
Ausnutzung der Vererbung hierarchisch strukturiert werden. Die vordefinierten
Exceptions unterteilen sich zum Beispiel in Prozessorfehler, Speicherfehler,
Ein- / Ausgabefehler und mathematische Fehler bei Integer- und
Flieсkommaoperationen.
Dieses System sollten Sie auch bei neuen Exceptions beibehalten, da es
Бbersichtlicher ist und die Implementierung von Exception-Handlern erleichtert.
Wenn Sie eine Reihe von Exceptions fБr einen neuen Anwendungsfall oder fБr eine
neue Klasse deklarieren, dann tun sie dies, indem Sie zuerst eine
Basis-Exception einfБhren, von der Sie die weiteren ableiten.
type
ENetworkError = class(Exception);
EWrongUserName = class(ENetworkError);
EWrongPassword = class(ENetworkError);
Es ist mФglich, einer Exception beliebige Komponenten in Form von Feldern,
Methoden oder Eigenschaften hinzuzufБgen. Diese Komponenten, meist werden es
Felder mit detaillierten Fehlerinformationen sein, kФnnen dann vom
Exception-Handler benutzt werden.
Die Basisklasse Exception besitzt bereits Felder zur Aufnahme einer
Fehlermeldung und einer Fehleradresse. Die Exceptions, die sich mit Ein- /
Ausgabefehlern beschДftigen, fБgen ein Feld mit dem Fehlercode der
fehlgeschlagenen Operation hinzu.
type
EInOutError = class(Exception)
ErrorCode: Integer;
end;
Achtung: Im Gegensatz zu Borland Delphi mБssen Sie nicht die zusДtzliche Unit
SysUtils einbinden, um die Fehlerbehandlung Бber Exceptions zu aktivieren.
SДmtliche Fehler des Laufzeitsystems werden bereits Бber Exceptions
abgewickelt. Der Basistyp Exception ist in der Unit System deklariert.
AuslФsen von Exceptions
Eine Exception wird durch das SchlБsselwort raise und die Angabe einer
Exception-Instanz ausgelФst. Diese Instanz kann und wird meistens genau an
dieser Stelle durch einen Konstruktor-Aufruf erzeugt. Es ist aber durchaus
mФglich, wenngleich meist БberflБssig, die Instanz vorher zu erzeugen.
<Syntaxdiagramm: Raise>
Der Konstruktor von Exception erwartet als Parameter einen String. Sie kФnnen
darin beim AuslФsen der Exception eine Fehlermeldung angeben. Sollte das
Programm mangels eines passenden Exception-Handlers abgebrochen werden, gibt es
zuvor diese Meldung aus.
if Password <> 'top secret' then
raise EWrongPassword.Create('Illegal password.');
Achtung: Im Unterschied zu Borland Delphi ist es derzeit nicht mФglich, eine
Fehleradresse mit Hilfe des SchlБsselwortes at anzugeben. Als Fehleradresse
wird stets die Adresse innerhalb des Codesegmentes angenommen, an der die
Exception ausgelФst wurde.
Behandeln von Exceptions
Wenn Sie nicht wollen, daс Ihr Programm beim Auftreten einer Exception mit
einer Fehlermeldung abbricht, dann mБssen Sie auf die Exception reagieren. Dazu
Бberwachen Sie kritische Codesequenzen und fБgen ihnen einen Exception-Block
hinzu. Der Exception-Block enthДlt Code, der nur im Fehlerfall ausgefБhrt
werden soll. Er kann einen einen oder mehrere Exception-Handler bereitstellen,
die Fehler innerhalb der Бberwachten Codesequenz behandeln.
Wenn innerhalb des Бberwachten Abschnitts eine Exception ausgelФst wird, tritt
sofort der Exception-Block in Aktion.
Wird der Бberwachte Abschnitt fehlerfrei beendet, wird die AusfБhrung hinter
dem Exception-Block wiederaufgenommen.
Bei verschachtelten Бberwachten Codesequenzen genieсt stets der Exception-Block
die hФchste PrioritДt, der zur innersten Бberwachten Sequenz gehФrt. Behandelt
er eine aufgetretene Exception nicht, wird sie nach auсen durchgereicht.
Ъberwachte Codesequenzen und Exception-Handler
<Syntaxdiagramm: Try_Except>
Eine Anweisungsfolge, die von den SchlБsselwФrtern try..except eingeschlossen
wird, ist eine Бberwachte Codesequenz.
Wenn innerhalb dieser Codesequenz eine Exception ausgelФst wird, dann verzweigt
das Programm unmittelbar zu der Anweisungsfolge, die sich zwischen den
SchlБsselwФrtern except..end befindet und als Exception-Block bezeichnet wird.
Dieser Exception-Block kann einen oder mehrere Exception-Handler bereitstellen,
um bestimmte Fehler zu behandeln. Er kann auch einen universellen
Exception-Handler beinhalten, der jede Art von Fehler behandelt.
try
/*Die folgenden Anweisungen sind kritisch. Wenn die Datei
nicht geФffnet werden kann, oder sich wДhrend des Lesens
Probleme einstellen, dann wird eine Exception ausgelФst.
Das Programm verzweigt dann unmittelbar zum EXCEPT..END-
Block, der den Fehler behandeln solle. */
Assign(F, 'c:\userdata\report.txt');
Reset(F);
...
Close(F);
except
...
end;
Der Exception-Block kann auf die Fehlerbedingung reagieren, muс dies aber nicht
tun. Ob ein Exception-Block auf eine bestimmte Art von Exception reagiert, kann
vom Programmierer festgelegt werden. Im allgemeinsten Fall werden die
Exceptions nicht weiter differenziert. Es ergibt sich ein universeller
Exception-Handler, der jede Art von Exception behandelt.
except
/*Dies ist ein universeller Exception-Handler. Der Code
zwischen EXCEPT..END wird in jedem Fehlerfall ausgefБhrt.
Der Fehler wird dadurch beseitigt. */
WriteLn('Unbekannter Fehler beim Lesen der Datei.');
end;
Durch Einsatz der SchlБsselwФrter on..do kann die Arbeit des Exception-Blockes
auf eine oder mehrere Arten von Exceptions eingeschrДnkt werden. Jedes on..do
definiert einen Handler fБr eine bestimmte Klasse von Exceptions.
except
/*Dieser Exception-Handler bearbeitet nur Fehler vom Typ
'EFileNotFound'. Alle weiteren Exceptions reicht er an die
nДchsthФhere Ebene weiter. */
on EFileNotFound do
WriteLn('Die angegebene Datei existiert nicht.');
end;
Es kФnnen mit on..do beliebig viele Exception-Handler angegeben werden. Das
Laufzeitsystem geht sie im Fehlerfall der Reihe nach durch. Sobald ein
passender Exception-Handler gefunden wird, wird dessen Code ausgefБhrt, und der
Fehler wird beseitigt. Es wird niemals mehr als ein Exception-Handler
ausgefБhrt.
Beachten Sie, daс ein Exception-Handler auch immer alle Nachfahren der Klasse
behandelt, fБr die er installiert wurde. Deshalb sollten speziellere
Exception-Handler in der Liste stets weiter vorn stehen, da sie sonst niemals
erreicht werden.
Ausgehend davon, daс alle Exceptions, die im Zusammenhang mit Dateioperationen
stehen, einen gemeinsamen Vorfahren EInOutError besitzen, kФnnte eine
Fehlerbehandlung etwa wie folgt aussehen:
except
/*Diese Exception-Handler bearbeiten eine Reihe von Fehlern.
Die spezielleren Handler stehen dabei weiter vorn, die
allgemeineren am Ende der Liste. */
on EFileNotFound do
WriteLn('Die angegebene Datei existiert nicht.');
on EAccessDenied do
WriteLn('Zugriff auf die Datei verweigert.');
on EInvalidFilename do
WriteLn('UngБltiger Dateiname.');
on EInOutError do
WriteLn('Ein-/Ausgabefehler beim Lesen der Datei.');
end;
Bei Angabe einer Liste von Exception-Handlern mit on..do ist es auсerdem
mФglich, im Anschluс an den letzten Exception-Handler mit else einen weiteren
anzugeben, der alle Exceptions behandelt, auf die nicht bereits einer der
speziellen Handler reagiert hat.
except
/*Diese Exception-Handler bearbeiten eine Reihe von Fehlern.
Die spezielleren Handler stehen dabei weiter vorn, die
allgemeineren am Ende der Liste. Fehler, die nicht mit der
Ein-/Ausgabeoperation in Zusammenhang stehen, werden durch
den ELSE-Teil behandelt. */
on EFileNotFound do
WriteLn('Die angegebene Datei existiert nicht.');
on EAccessDenied do
WriteLn('Zugriff auf die Datei verweigert.');
on EInvalidFilename do
WriteLn('UngБltiger Dateiname.');
on EInOutError do
WriteLn('Ein-/Ausgabefehler beim Lesen der Datei.');
else
WriteLn('Unbekannter Fehler beim Lesen der Datei.');
end;
Wird eine Exception innerhalb des Exception-Blocks behandelt, dann wird sie
beseitigt, und die AusfБhrung wird unmittelbar hinter dem except..end-Block
wiederaufgenommen. Behandelt keiner der Exception-Handler den aufgetretenen
Fehler, dann bleibt die Exception bestehen. Der eingebaute Exception-Handler
von Speed-Pascal tritt in Aktion und beendet das Programm mit einer
Fehlermeldung, es sei denn, der gesamte Abschnitt befindet sich innerhalb einer
weiteren mittels try..except Бberwachten Codesequenz.
Tritt innerhalb der Бberwachten Codesequenz gar keine Exception auf, dann
verzweigt das Programm nach Beendigung des try..except-Blockes unmittelbar
hinter das Ende des Exception-Blockes. Es besteht in diesem Fall keine
Veranlassung, die Fehlerbehandlung aufzurufen.
Beachten Sie, daс es sich bei der Verzweigung im Fehlerfall um einen
unmittelbaren Sprung handelt, nicht um einen Prozeduraufruf. Der Stack wird
soweit aufgerДumt, daс er sich im gleichen Zustand befindet wie beim Betreten
der Бberwachten Codesequenz. Danach wird die Exception-Behandlung angesprungen.
Nach der Behandlung der Exception kann kein RБcksprung an die Fehleradresse
mehr stattfinden. Stattdessen wird die AusfБhrung hinter dem except..end-Block
wiederaufgenommen.
Geschachtelte Exception-Handler
Es ist mФglich, Бberwachte Codesequenzen zu schachteln. Dies kann entweder
direkt durch Schachtelung von try..except-Anweisungen geschehen oder indirekt
dadurch, daс innerhalb der Бberwachten Codesequenz aufgerufene Prozeduren oder
Funktionen selbst lokal Codesequenzen Бberwachen. In beiden FДllen werden die
zugehФrigen except..end-BlФcke auf eine Art Stapel gelegt, wobei sich der zum
zuletzt betretenen try..except-Block gehФrende zuoberst befindet.
Tritt eine Exception auf, so wird zum obersten auf dem Stapel befindlichen
except..end-Block verzweigt. Dieser gehФrt zu genau der Бberwachten
Codesequenz, die zuletzt betreten und noch nicht wieder beendet wurde. Falls
hier ein Exception-Handler existiert, der die aufgetretene Exception behandelt,
findet die Behandlung statt, und die Exception wird gelФscht. Die AusfБhrung
wird hinter dem except..end-Block wiederaufgenommen.
Behandelt keiner der Exception-Handler die aufgetretene Exception, so wird der
except..end-Block vom Stapel abgerДumt, und es wird zum nДchsten
except..end-Block verzeigt. Dies geschieht solange, bis ein Exception-Handler
den aufgetretenen Fehler behandelt. Existiert kein solcher Exception-Handler,
tritt die eingebaute Fehlerbehandlung von Speed-Pascal in Aktion. Das Programm
wird abgebrochen.
AuslФsen von Exceptions im Exception-Handler
Bei Schachtelung von Бberwachten Codesequenzen kann es sinnvoll sein, lokal auf
den Fehler zu reagieren, aber die Fehlerbedingung bestehen zu lassen, damit die
nДchsthФhere Ebene auch darauf reagieren kann. In diesem Fall kann innerhalb
des except..end-Blockes durch Einsatz von raise ohne jeden weiteren Parameter
dafБr gesorgt werden, daс die aufgetretene Exception unmittelbar wieder
ausgelФst wird. Der except..end-Block wird vom Stapel abgerДumt, und es wird so
verfahren, als wДre die Exception nicht behandelt worden. Diese spezielle
syntaktische Variante von raise kann nur innerhalb eines Exception-Handlers
benutzt werden.
Es ist auch mФglich, mittels der normalen Syntax von raise eine andere
Exception innerhalb eines except..end-Blockes auszulФsen. Wird diese Exception
nicht lokal innerhalb des except..end-Blockes behandelt, dann verdrДngt sie die
ursprБngliche Exception. Diese wird gelФscht, der except..end-Block wird vom
Stapel abgerДumt, und die Behandlung der neuen Exception wird an die
nДchsthФhere Ebene verwiesen.
Dies kann manchmal sinnvoll sein, wenn mehrere lokal auftretende Exceptions
abgefangen und in einen gemeinsamen Typ von Exception umgewandelt werden
sollen, der von der nДchsthФheren Ebene behandelt wird.
Es kann aber auch zu unerwБnschten Ergebnissen fБhren, wenn nicht beachtet
wird, daс innerhalb eines - mФglicherweise sehr komplexen - Exception-Handlers
wiederum Fehler auftreten kФnnen. In diesem Fall sollten Sie den kritischen
Bereich innerhalb des Exception-Handlers wiederum in einen try..except-Block
mit anschlieсendem except..end-Block einschlieсen. Exceptions, die innerhalb
des inneren Exception-Handlers nicht bearbeitet werden, verdrДngen die
Exception des Дuсeren Handlers und werden an die nДchsthФhere Ebene verwiesen.
Zugriff auf die Exception-Instanz
Es ist mФglich, innerhalb eines Exception-Handlers auf die Exception-Instanz
zuzugreifen. Das ist sinnvoll, wenn die aufgetretene Exception zusДtzliche
Informationen enthДlt, die vom Handler ausgewertet werden sollen.
Zum Zugriff auf die Exception-Instanz kann einem durch on..do eingeleiteten
Exception-Handler ein Bezeichner gefolgt von einem Doppelpunkt vorangestellt
werden. Innerhalb dieses einen Exception-Handlers steht dann unter dem
angegebenen Bezeichner die Instanz der aufgetretenen Exception zur VerfБgung.
Der Typ des Bezeichners entspricht der Exception-Klasse, die der Handler
bearbeitet, ist also eine passende Objektreferenz.
Das folgende Beispiel soll die MФglichkeiten verdeutlichen. Die
Exception-Klasse EInOutError und deren Nachfahren besitzen ein Feld ErrorCode,
das den Fehlercode des aufgetretenen Fehlers enthДlt. Diesen Fehlercode kФnnte
sich ein Exception-Handler zunutze machen.
try
Assign(F, 'c:\userdata\report.txt');
Reset(F);
...
Close(F);
except
on E: EInOutError do
WriteLn('Ein-/Ausgabefehler ', E.ErrorCode, ' beim Lesen der Datei.');
else
WriteLn('Unbekannter Fehler beim Lesen der Datei.');
end;
Beachten Sie, daс es nicht nФtig ist, die Variable mittels var zu deklarieren.
Der Bezeichner wird an der entsprechenden Stelle generiert und ist nur
innerhalb des zugehФrigen Exception-Handlers gБltig.
Achtung: Sie haben Бber den Bezeichner auch Zugriff auf alle Methoden der
Instanz. Gehen Sie sehr vorsichtig mit dieser MФglichkeit um. Versuchen Sie
insbesondere niemals, die Exception-Instanz mittels Destroy oder Free selbst zu
lФschen. Das ist Aufgabe des Exception-Handlers. Der Versuch, dies 'von Hand'
zu erledigen, wБrde das Laufzeitsystem derart durcheinander bringen, daс das
Programm mit hoher Wahrscheinlichkeit abstБrzt.
SchБtzen von Resourcen
Wenn innerhalb einer fehleranfДlligen Codesequenz Resourcen belegt werden, dann
ist es notwendig, diese auch beim Auftreten einer Exception wieder freigeben zu
kФnnen. Anderenfalls gehen die Resourcen mФglicherweise dauerhaft verloren, da
das Auftreten der Exception einen direkten Sprung zum Exception-Handler zufolge
hat. Die Anweisungen, die die Benutzung der Resource abschlieсen und diese
freigeben, werden im Fehlerfall gar nicht erreicht.
Die try..finally-Anweisung dient dazu, die Freigabe von Resourcen auch im
Fehlerfall zu garantieren.
<Syntaxdiagramm: Try_Finally>
Um zu garantieren, daс eine bestimmte Codesequenz auch beim Auftreten einer
Exception ausgefБhrt wird, gehen Sie wie folgt vor:
Setzen Sie die Anweisungen, die die Resource allokieren, vor den
try..finally-Block.
Schlieсen Sie die Arbeit mit der Resource in den try..finally-Block ein.
Schlieсen Sie die Anweisungen zum Freigeben der Resource in den
finally..end-Block ein.
/*Zuerst wird die Resource allokiert. In diesem Fall wird
versucht, eine Datei zu Фffnen. */
Assign(F, 'c:\userdata\report.txt');
Reset(F);
try
// Lesen der Datei. MФglicherweise treten hier Fehler auf.
...
finally
// Die Datei soll in jedem Fall geschlossen werden.
Close(F);
end;
Wird innerhalb des try..finally-Blocks eines Exception ausgelФst, verzweigt
das Programm zuerst zum finally..end-Block. Wenn dieser beendet ist, wird die
Exception an den zustДndigen Exception-Handler weitergereicht, falls ein
solcher existiert.
Beachten Sie, daс die Exception innerhalb eines finally..end-Abschnittes nicht
behandelt wird. Dieser Bereich ist kein Exception-Handler, er garantiert nur
die AusfБhrung einer Reihe von Anweisungen unabhДngig davon, ob ein Fehler
auftritt oder nicht. Um auf den Fehler zu reagieren, muс der gesamte Abschnitt
geschБtzt und mit einem Exception-Handler versehen werden. Es ist nicht
mФglich, finally und except zu kombinieren, etwa derart, daс der except-Block
auf den finally-Block folgt.
try
/*Zuerst wird die Resource allokiert. In diesem Fall wird
versucht, eine Datei zu Фffnen. */
Assign(F, 'c:\userdata\report.txt');
Reset(F);
try
// Lesen der Datei. MФglicherweise treten hier Fehler auf.
...
finally
// Die Datei soll in jedem Fall geschlossen werden.
Close(F);
end;
except
WriteLn('Es trat ein Fehler beim Lesen der Datei auf.');
end;
Es ist mФglich, try..finally-BlФcke ineinander zu schachteln. Es ist dann auf
jeder Ebene sichergestellt, daс die Anweisungen zwischen finally..end
ausgefБhrt werden, bevor zur nДchsthФheren Ebene verzweigt wird.
Achtung: Falls innerhalb eines finally..end-Blockes eine Exception ausgelФst
wird, die dort nicht lokal behandelt wird, verdrДngt sie die ursprБngliche
Exception und verzeigt zum nДchsten zustДndigen Exception-Handler. Das
Verhalten ist zwar definiert, aber es ist nicht empfehlenswert, daс Sie davon
Gebrauch machen. Stellen Sie stattdessen sicher, daс Exceptions, die in
finally..end-BlФcken auftreten, auch dort behandelt werden.
Exceptions und Objekte
Exceptions kФnnen ohne EinschrДnkung innerhalb von Objektmethoden verwendet
werden. Es gibt jedoch eine Besonderheit zu beachten. Wenn innerhalb eines
Konstruktor-Aufrufes eine Exception auftritt, die dort nicht behandelt wird,
dann wird zuerst der Destruktor aufgerufen, bevor zum mФglicherweise
vorhandenen Exception-Handler verzweigt wird.
Damit verhalten sich Konstruktoren exakt so, als wДren sie auf die folgende
Weise implementiert:
constructor TMyClass.Create;
begin
try
// GewБnschte Anweisungen zwischen BEGIN und END
except
Destroy;
raise;
end;
end;
Aufgrund dieses Verhaltens ist es wichtig, daс ein Destruktor auch mit nicht
vollstДndig initialisierten Objekten umzugehen weiс, denn er kann theoretisch
von jeder beliebigen Stelle des Konstruktors aus aufgerufen werden.
ΓòÉΓòÉΓòÉ 1.11. Vordefinierte Exceptions ΓòÉΓòÉΓòÉ
ΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûäΓûä
Vordefinierte Exceptions
ΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇΓûÇ
Basisklasse Exception
SДmtliche Laufzeitfehler von Speed-Pascal werden Бber Exceptions abgewickelt.
Dazu sind in der Unit System bereits eine Reihe von Exceptions vordefiniert.
Exception = class(TObject)
public
constructor Create(const Msg: String);
destructor Destroy; override;
property Message: string; // lesen / schreiben
property MessagePtr: PString; // nur lesen
end;
ExceptClass = class of Exception.
Exception ist die Basisklasse fБr Ausnahmebedingungen. Von ihr sollten alle
weiteren Exception-Klassen abgeleitet werden. Exception selbst wird selten
ausgelФst, meist werden es spezialisierte Nachkommen sein, die auf bestimmte
Fehler hinweisen. Alle Exception-Klassen erben von Exception Konstruktor,
Destruktor sowie die Eigenschaft Message, die die Fehlermeldung enthДlt, die
beim AuslФsen der Exception an den Konstruktor Бbergeben wird.
Software-generierte Exceptions
EProcessTerm = class(Exception);
EProcessTerm wird vom Betriebssystem ausgelФst, wenn der Prozeс beendet
werden soll.
Hardware-generierte Exceptions
EProcessorException = class(Exception);
EProcessorException ist der Vorfahr fБr weitere vom Prozessor ausgelФste
Exceptions. Es wird niemals EProcessorException selbst ausgelФst, sondern
stets ein Nachkomme, der auf eine bestimmte Ausnahmebedingung hinweist.
Im Unterschied zu EFault weist EProcessorException nicht unbedingt auf
einen Fehler hin, denn die Nachfahren EBreakpint und ESingleStep dienen
zum Debuggen eines Programms.
EFault = class(EProcessorException);
EFault ist der Vorfahr fБr alle vom Prozessor ausgelФsten Exceptions, die
auf einen Fehler hinweisen. Es wird niemals EFault ausgelФst, sondern
stets ein Nachkomme, der auf eine bestimmte Fehlerbedingung hinweist.
Im Gegensatz zu EProcessorException weist EFault immer auf einen Fehler
hin.
EGPFault = class(EFault);
EGPFault wird ausgelФst, wenn eine allgemeine Schutzverletzung auftritt.
Eine Schutzverletzung liegt vor, wenn ein Prozeс auf Speicherbereiche
zugreift, die nicht innerhalb seines Adreсraums liegen.
Meist deutet dieser Fehler darauf hin, daс eine illegale Zeigeroperation
durchgefБhrt wurde oder daс mit einem nicht korrekt initialisierten
Objekt gearbeitet wurde.
EStackFault = class(EFault);
EStackFault wird ausgelФst, wenn nicht genБgend Stackspeicher vorhanden
ist, um die aktuelle Operation durchzufБhren. Prozeduren und Funktionen
reservieren beim Aufruf den Speicher fБr ihre lokalen Variablen auf dem
Stack.
Wenn dieser Fehler auftritt, dann teilen Sie dem Programm mehr
Stackspeicher zu oder vermeiden Sie die Benutzung zu vieler lokaler
Variablen, insbesondere bei Rekursion.
Diese Exception kann nur auftreten, wenn die StackprБfung aktiviert ist,
die Operation also im Modus $S+ compiliert wird.
EPageFault = class(EFault);
EPageFault wird vom Prozessor ausgelФst, wenn ein Seitenfehler auftritt.
Ein Seitenfehler liegt vor, wenn der Prozeс auf eine Speicherseite
zugreifen will, die momentan ausgelagert ist.
Dieser Fehler sollte normalerweise nicht auftreten, da sich das
Betriebssystem um die Verwaltung des physikalischen und virtuellen
Speichers kБmmert und die entsprechende Seite automatisch wieder
einlagert.
EInvalidOpCode = class(EFault);
EInvalidOpCode wird vom Prozessor ausgelФst, wenn dieser auf eine
illegale Maschineninstruktion trifft.
EBreakpoint = class(EProcessorException);
EBreakpoint wird vom Prozessor ausgelФst, wenn dieser auf einen
Breakpoint trifft.
ESingleStep = class(EProcessorException);
ESingleStep wird vom Prozessor nach jedem durchgefБhrten Befehl
ausgelФst, wenn das Programm im Einzelschrittmodus abgearbeitet wird.
Exceptions bei Speicher-Operationen
EOutOfMemory = class(Exception);
EOutOfMemory wird ausgelФst, wenn eine angeforderte Menge Speicher nicht
mehr auf dem Heap zur VerfБgung steht.
EInvalidPointer = class(Exception);
EInvalidPointer wird ausgelФst, wenn ein Programm versucht, einen
Speicherbereich zu deallokieren, der nicht zuvor allokiert wurde oder
bereits freigegeben ist.
EInvalidHeap = class(Exception);
EInvalidHeap wird ausgelФst, wenn der Heapbereich der Applikation
beschДdigt ist. Dies kann vorkommen, wenn die Applikation durch
fehlerhafte Zeigeroperationen Speicherbereiche Бberschreibt, die fБr die
Heapverwaltung wichtig sind.
Exceptions bei Ein- / Ausgabeoperationen
EInOutError = class(Exception)
public
ErrorCode: Integer;
end;
EInOutError ist der Vorfahr fБr eine Reihe von Exceptions, die auf Ein- /
Ausgabe-Fehler hinweisen. Alle Exceptions dieser Art besitzen ein
zusДtzliches Feld, das den Fehlercode aufnehmen kann, der bei der
Operation aufgetreten ist.
Damit diese Exception und ihre Nachfahren auftreten kФnnen, muс die Ein-
/ Ausgabe-ЪberprБfung aktiviert sein, die entsprechende Operation muс
also im Modus $I+ compiliert werden.
EAccessDenied = class(EInOutError);
EAccessDenied wird ausgelФst, wenn das Щffnen einer Datei fehlschlДgt,
weil entweder die Attribute der Datei ein Щffnen zum Schreiben nicht
erlauben, oder weil die Datei bereits von einem anderen Prozeс exklusiv
geФffnet wurde.
EDiskFull = class(EInOutError);
EDiskFull wird ausgelФst, wenn die Diskette oder Festplatte, auf die der
Prozeс schreiben will, voll ist.
EEndOfFile = class(EInOutError);
EEndOfFile wird ausgelФst, wenn der Prozeс versucht, Бber das Ende einer
Datei hinaus zu lesen.
EFileNotFound = class(EInOutError);
EFileNotFound wird ausgelФst, wenn der Prozeс versucht, eine Datei zu
Фffnen, die nicht existiert.
EInvalidFileName = class(EInOutError);
EInvalidFileName wird ausgelФst, wenn ein illegaler Dateiname benutzt
wird, der zum Beispiel zu lang ist oder Sonderzeichen enthДlt, die nicht
Teil eines Dateinamens sein dБrfen.
EInvalidInput = class(EInOutError);
EInvalidInput wird ausgelФst, wenn der Benutzer des Programms eine
ungБltige Eingabe macht.
ETooManyOpenFiles = class(EInOutError);
ETooManyOpenFiles wird ausgelФst, wenn zu viele Dateien gleichzeitig
geФffnet sind. Wenn der Fehler auftritt, erhФhen Sie die entsprechende
Einstellung des Betriebssystems, um mehr gleichzeitig geФffnete Dateien
zu erlauben.
Mathematische Exceptions bei Integer-Operationen
EIntError = class(Exception);
EIntError ist der gemeinsame Vorfahr einer Reihe von Exceptions, die auf
Fehler bei mathematischen Operationen mit Integer-Werten hinweisen.
EDivByZero = class(EIntError);
EDivByZero wird ausgelФst, wenn eine Division durch Null versucht wird.
ERangeError = class(EIntError);
ERangeError wird ausgelФst, wenn fБr das Ergebnis einer mathematischen
Operation eine BereichsprБfung fehlschlДgt. Damit diese Exception
auftreten kann, muс die Operation im Modus $R+ compiliert werden.
EIntOverflow = class(EIntError);
EIntOverflow wird ausgelФst, wenn das Ergebnis einer mathematischen
Operation einen Ъberlauf produziert hat. Damit dieser Fehler auftreten
kann, muс die Operation im Modus $Q+ compiliert werden.
Mathematische Exceptions bei Flieсkomma-Operationen
EMathError = class(Exception);
EIntError ist der gemeinsame Vorfahr einer Reihe von Exceptions, die auf
Fehler bei mathematischen Operationen mit Integer-Werten hinweisen.
EInvalidOp = class(EMathError);
EInvalidOp wird ausgelФst, wenn eine ungБltige mathematisch Operation
versucht wird, z.B. das Ziehen der Wurzel aus einer negativen Zahl.
EZeroDivide = class(EMathError);
EZeroDivide wird ausgelФst, wenn eine Division durch Null versucht wird.
EOverflow = class(EMathError);
EOverflow wird ausgelФst, wenn das Ergebnis einer mathematischen
Operation einen Ъberlauf produziert hat.
EUnderflow = class(EMathError);
EUnderflow wird ausgelФst, wenn das Ergebnis einer mathematischen
Operation so klein ist, daс es mit der PrДzision der Ergebnisvariablen
nicht mehr dargestellt werden kann.
Dieser Fehler tritt normalerweise nicht auf. Stattdessen liefert
Speed-Pascal den Wert Null als Ergebnis zurБck. Durch Оndern des
Kontrollwortes des mathematischen Koprozessors kann das AuslФsen dieser
Exception jedoch ermФglicht werden.
Exceptions bei Typwandlungen
EInvalidCast = class(Exception);
EInvalidCast wird ausgelФst, wenn eine Typwandlung mit Hilfe des
Operators as fehlschlДgt.
EConvertError = class(Exception);
EConvertError wird bei einer Reihe von Konvertierungsfunktionen
ausgelФst, wenn der Quellwert ungБltig ist.