Deklarationen und Anweisungen (Delphi)
Nach oben zu Grundlegende syntaktische Elemente - Index
Dieses Thema enthält eine Beschreibung der Deklarations- und Anweisungssyntax in Delphi.
Neben der uses-Klausel (und reservierten Wörtern zur Abgrenzung von Abschnitten in Units, wie implementation) besteht ein Programm ausschließlich aus Deklarationen und Anweisungen, die in so genannten Blöcken organisiert sind.
Dieses Thema enthält Informationen zu folgenden Bereichen:
- Deklarationen
- Einfache Anweisungen, wie z. B. Zuweisungen
- Strukturierte Anweisungen, wie bedingte Tests (z. B. if-then und case), Iterationen (z. B. for und while).
Inhaltsverzeichnis
Deklarationen
Die Namen von Variablen, Konstanten, Typen, Feldern, Eigenschaften, Prozeduren, Funktionen, Programmen, Units, Bibliotheken und Packages werden Bezeichner genannt. (Numerische Konstanten wie 26057 sind keine Bezeichner.) Damit ein Bezeichner verwendet werden kann, muss er zuerst deklariert werden. Ausnahmen bilden einige vordefinierte Typen, Routinen und Konstanten, die vom Compiler auch ohne Deklaration interpretiert werden können, die Variable Result innerhalb eines Funktionsblocks und die Variable Self in einer Methodenimplementierung.
Eine Deklaration definiert den Bezeichner und sorgt dafür, dass bei Bedarf Speicher für ihn reserviert wird. Zum Beispiel:
var Size: Extended;
Diese Anweisung deklariert eine Variable namens Size, die einen Extended-Wert (Typ Real) enthält:
function DoThis(X, Y: string): Integer;
Diese Anweisung deklariert eine Funktion mit dem Namen DoThis, die zwei Strings als Argumente übernimmt und einen Integerwert zurückgibt. Jede Deklaration wird durch einen Strichpunkt abgeschlossen. Werden mehrere Variablen, Konstanten, Typen oder Labels gleichzeitig deklariert, genügt es, das entsprechende reservierte Wort nur einmal anzugeben:
var
Size: Extended;
Quantity: Integer;
Description: string;
Die Syntax und der Ort einer Deklaration sind vom Typ des Bezeichners abhängig, der definiert werden soll. In der Regel erfolgen Deklarationen nur am Anfang eines Blocks bzw. am Anfang des interface- oder implementation-Abschnitts einer Unit (nach der uses-Klausel). Die Konventionen für die Deklaration von Variablen, Konstanten, Typen, Funktionen usw. werden in den betreffenden Themen erläutert.
Hinweis-Direktiven
Die Hinweis-Direktiven platform, deprecated und library können an jede Deklaration angehängt werden. Diese Direktiven erzeugen beim Compilieren Warnungen. Hinweis-Direktiven können für Typdeklarationen, Variablendeklarationen, Klassen-, Interface- und Strukturdeklarationen, Felddeklarationen innerhalb von Klassen oder Records, Prozedur-, Funktions- und Methodendeklarationen sowie Unit-Deklarationen angewendet werden.
Tritt eine Hinweis-Direktive in einer Unit-Deklaration auf, bedeutet dies, dass der Hinweis für alle Elemente in der Unit gilt. Die Unit OleAuto.pas im Windows 3.1-Stil ist beispielsweise unter Windows völlig veraltet. Jeder Verweis auf diese Unit oder auf ein Symbol in dieser Unit erzeugt die Meldung, dass diese veraltet ist.
Die Hinweis-Direktive platform bei einem Symbol oder einer Unit gibt an, dass diese eventuell nicht existiert oder die Implementierungen auf unterschiedlichen Plattformen wesentlich voneinander abweichen. Die Hinweis-Direktive library bei einem Symbol oder einer Unit gibt an, dass der Quelltext eventuell nicht existiert oder die Implementierungen in unterschiedlichen Bibliotheksarchitekturen wesentlich voneinander abweichen.
Die Direktiven platform und library enthalten keine Angaben über die Plattform oder Bibliothek. Wenn Sie beabsichtigen, plattformunabhängigen Code zu erstellen, müssen Sie nicht wissen, für welche Plattform ein Symbol spezifisch ist. Es reicht aus, das Symbol für irgendeine Plattform als spezifisch zu kennzeichnen, damit Sie wissen, dass es eventuell bei der Portabilität Probleme bereiten könnte.
In einer Prozedur- oder Funktionsdeklaration sollte die Hinweis-Direktive durch einen Strichpunkt vom Rest der Deklaration getrennt werden. Beispiele:
procedure SomeOldRoutine; stdcall; deprecated;
var
VersionNumber: Real library;
type
AppError = class(Exception)
...
end platform;
Wenn der Quelltext im Status {$HINTS ON} {$WARNINGS ON} compiliert wird, erzeugt jede Referenz auf einen Bezeichner, der mit einer dieser Direktiven deklariert wurde, einen entsprechenden Hinweis oder eine entsprechende Warnung. Mit der Direktive platform werden Elemente gekennzeichnet, die für ein bestimmtes Betriebssystem (z. B. Windows) spezifisch sind. Mit deprecated wird angegeben, dass ein Element veraltet ist oder nur aus Gründen der Abwärtskompatibilität unterstützt wird. Die Direktive library wird verwendet, um auf die Abhängigkeit von einem bestimmten Bibliotheks- oder Komponenten-Framework hinzuweisen.
Der Delphi-Compiler erkennt auch die Hinweis-Direktive experimental. Mit dieser Direktive werden Units gekennzeichnet, die sich in einem instabilen Entwicklungszustand befinden. Beim Compilieren einer Anwendung, die diese Unit verwendet, wird eine Warnung ausgegeben.
Weitere Informationen zu den Delphi-Hinweis-Direktiven finden Sie unter Warnungs-Direktiven in Methodendeklarationen. Alle Delphi-Direktiven sind unter Direktiven aufgelistet.
Anweisungen
Anweisungen definieren algorithmische Aktionen in einem Programm. Einfache Anweisungen (z. B. Zuweisungen und Prozeduraufrufe) können kombiniert werden. Auf diese Weise lassen sich Schleifen, bedingte Anweisungen und andere strukturierte Anweisungen erstellen.
Mehrere Anweisungen in einem Block sowie im initialization- und finalization-Abschnitt einer Unit werden durch Strichpunkte voneinander getrennt.
Einfache Anweisungen
Einfache Anweisungen enthalten keine anderen Anweisungen. Zu ihnen gehören Zuweisungen, Aufrufe von Prozeduren und Funktionen sowie goto-Sprünge.
Zuweisungen
Eine Zuweisung hat folgendes Format:
variable := expression
variable ist eine beliebige Variablenreferenz, d. h. eine Variable, eine Variablenumwandlung, ein dereferenzierter Zeiger oder eine Komponente einer strukturierten Variable. expression kann jeder zuweisungskompatible Ausdruck sein (in einem Funktionsblock kann die Variable durch den Namen der definierten Funktion ersetzt werden; siehe Prozeduren und Funktionen (Delphi).) Das Symbol := wird auch als Zuweisungsoperator bezeichnet.
Eine Zuweisung ersetzt den aktuellen Wert der Variable durch den Wert des Ausdrucks. Zum Beispiel:
I := 3;
Mit dieser Anweisung wird der Variable I der Wert 3 zugewiesen. Die Variablenreferenz auf der linken Seite der Zuweisung kann auch im Ausdruck auf der rechten Seite enthalten sein. Zum Beispiel:
I := I + 1;
Hier wird der Wert von I inkrementiert. Weitere Beispiele für Zuweisungen:
X := Y + Z;
Done := (I >= 1) and (I < 100);
Hue1 := [Blue, Succ(C)];
I := Sqr(J) - I * K;
Shortint(MyChar) := 122;
TByteRec(W).Hi := 0;
MyString[I] := 'A';
SomeArray[I + 1] := P^;
TMyObject.SomeProperty := True;
Prozedur- und Funktionsaufrufe
Ein Prozeduraufruf besteht aus dem Namen einer Prozedur (mit oder ohne Qualifizierer) und (falls erforderlich) einer Parameterliste. Hier einige Beispiele für Prozeduraufrufe:
PrintHeading;
Transpose(A, N, M);
Find(Smith, William);
Writeln('Hello world!');
DoSomething();
Unit1.SomeProcedure;
TMyObject.SomeMethod(X,Y);
Wenn die erweiterte Syntax ({$X+}) aktiviert ist, können auch Funktionsaufrufe, wie z. B. Aufrufe von Prozeduren, als Anweisungen behandelt werden:
MyFunction(X);
Bei dieser Verwendung eines Funktionsaufrufs wird der Rückgabewert verworfen.
Weitere Informationen zu Prozeduren und Funktionen finden Sie unter Prozeduren und Funktionen (Delphi).
Goto-Anweisungen
Die Syntax für goto-Anweisungen lautet:
goto label
Eine goto-Anweisung setzt die Ausführung des Programms mit der Anweisung fort, die mit dem angegebenen Label gekennzeichnet ist. Zur Kennzeichnung einer Anweisung deklarieren Sie zunächst das Label. Dann fügen Sie vor der zu kennzeichnenden Anweisung das Label und einen Doppelpunkt ein:
label: statement
Labels werden folgendermaßen deklariert:
label label;
Es können auch mehrere Labels gleichzeitig deklariert werden:
label label1, ..., labeln;
Als Label kann jeder gültige Bezeichner und jede Zahl von 0 bis 4294967295 verwendet werden.
Da Labels keine Sprünge aus einer Prozedur bzw. Funktion in eine andere erlauben, müssen sich die Label-Deklaration, die gekennzeichnete Anweisung und die goto-Anweisung innerhalb eines Blocks befinden. (siehe "Blöcke und Gültigkeitsbereich" weiter unten). In einem Block darf jedes Label nur einmal zur Kennzeichnung einer Anweisung verwendet werden.
Zum Beispiel:
label StartHere;
...
StartHere: Beep;
goto StartHere;
Dieser Quelltext ergibt eine Endlosschleife, in der immer wieder die Prozedur Beep aufgerufen wird.
Sprünge in oder aus try-finally- oder try-except-Anweisungen sind nicht möglich.
Im Sinne eines guten Programmierstils sollten goto-Anweisungen so sparsam wie möglich eingesetzt werden. In bestimmten Fällen bieten sie sich aber an, um eine verschachtelte Schleife zu verlassen. Dazu ein Beispiel:
procedure FindFirstAnswer;
var X, Y, Z, Count: Integer;
label FoundAnAnswer;
begin
Count := SomeConstant;
for X := 1 to Count do
for Y := 1 to Count do
for Z := 1 to Count do
if ... { some condition holds on X, Y, and Z } then
goto FoundAnAnswer;
... { Code to execute if no answer is found }
Exit;
FoundAnAnswer:
... { Code to execute when an answer is found }
end;
Die goto-Anweisung wird hier eingesetzt, um eine verschachtelte Schleife zu verlassen. Sprünge in eine Schleife oder strukturierte Anweisung müssen vermieden werden, da dies zu unvorhersehbaren Ergebnissen führen könnte.
Strukturierte Anweisungen
Strukturierte Anweisungen setzen sich aus anderen Anweisungen zusammen. Sie werden eingesetzt, wenn andere Anweisungen sequenziell, wiederholt oder in Abhängigkeit von einer Bedingung ausgeführt werden sollen.
- Eine Verbundanweisung (auch als with-Anweisung bezeichnet) führt eine Folge von Teilanweisungen aus.
- Eine bedingte Anweisung (also eine if- oder case-Anweisung) führt nach der Auswertung einer Bedingung mindestens eine ihrer Teilanweisungen aus.
- Schleifenanweisungen (repeat-, while- und for-Schleifen) führen eine Folge von Teilanweisungen mehrmals hintereinander aus.
- Eine spezielle Anweisungsgruppe (einschließlich Konstruktionen mit raise, try...except und try...finally dienen zur Generierung und Behandlung von Exceptions. Einzelheiten zum Erzeugen und Behandeln von Exceptions finden Sie unter Exceptions (Delphi).
Verbundanweisungen
Eine Verbundanweisung setzt sich aus einer Folge von anderen (einfachen oder strukturierten) Anweisungen zusammen, die nacheinander ausgeführt werden. Die in einer Verbundanweisung enthaltenen Teilanweisungen sind zwischen den reservierten Wörtern begin und end eingeschlossen und durch Strichpunkte voneinander getrennt. Zum Beispiel:
begin
Z := X;
X := Y;
X := Y;
end;
Der letzte Strichpunkt vor end ist optional. Die Verbundanweisung könnte demnach auch folgendermaßen aussehen:
begin
Z := X;
X := Y;
Y := Z
end;
Verbundanweisungen sind wichtig, wenn die Syntax von Delphi eine einzelne Anweisung verlangt. Sie können in Programm-, Funktions- und Prozedurblöcke und in andere strukturierte Anweisungen (z. B. bedingte Anweisungen oder Schleifen) integriert werden. Zum Beispiel:
begin
I := SomeConstant;
while I > 0 do
begin
...
I := I - 1;
end;
end;
Es gibt auch Verbundanweisungen, die nur eine einzelne Teilanweisung enthalten. Wie runde Klammen in einem komplexen Ausdruck tragen die Wörter begin und end zur Vermeidung von Mehrdeutigkeiten und zur Verbesserung der Lesbarkeit bei. Es ist auch möglich, mit einer leeren Verbundanweisung einen Block zu erzeugen, der keine Aktion ausführt:
begin end;
with-Anweisungen
Eine with-Anweisung stellt eine einfache und bequeme Möglichkeit dar, die Felder eines Records oder die Felder, Eigenschaften und Methoden eines Objekts zu referenzieren. Die Syntax einer with-Anweisung lautet:
with obj do statement
oder:
with obj1, ..., objn do statement
obj ist ein Ausdruck mit einer Referenz auf einen Record, eine Objektinstanz, eine Klasseninstanz, eine Interface- oder Klassentyp-(Metaklassen-)Instanz. statement ist eine einfache oder eine strukturierte Anweisung. Innerhalb von statement können Felder, Eigenschaften und Methoden von obj nur über ihren Bezeichner referenziert werden. Die Angabe von Qualifizierern ist nicht erforderlich.
Ausgehend von den Deklarationen:
type
TDate = record
Day: Integer;
Month: Integer;
Year: Integer;
end;
var
OrderDate: TDate;
könnten Sie die folgende with-Anweisung:
with OrderDate do
if Month = 12 then
begin
Month := 1;
Year := Year + 1;
end
else
Month := Month + 1;
oder eine with-Anweisung schreiben:
if OrderDate.Month = 12 then
begin
OrderDate.Month := 1;
OrderDate.Year := OrderDate.Year + 1;
end
else
OrderDate.Month := OrderDate.Month + 1;
Wenn die Interpretation von obj die Indizierung von Arrays oder die Dereferenzierung von Zeigern verlangt, werden diese Aktionen einmal durchgeführt, bevor die Anweisung ausgeführt wird. Dies macht with-Anweisungen sowohl effizient als auch kurz. Außerdem können sich während der aktuellen Ausführung der with-Anweisung Zuweisungen an eine Variable innerhalb der Anweisung nicht auf die Interpretation von obj auswirken.
Jede Variablenreferenz und jeder Methodenname in einer with-Anweisung wird, wenn möglich, als Member des angegebenen Objekts bzw. Records interpretiert. Wenn in der with-Anweisung auf eine andere Variable oder Methode mit demselben Namen zugegriffen werden soll, ist ein Qualifizierer erforderlich:
with OrderDate do
begin
Year := Unit1.Year;
...
end;
Wenn auf with mehrere Objekte oder Records folgen, wird die gesamte Anweisung als Folge von verschachtelten with-Anweisungen behandelt. Die Anweisung:
with obj1, obj2, ..., objn do statement
ist gleichbedeutend mit:
with obj1 do
with obj2 do
...
with objn do
// statement
In diesem Fall wird jede Variablenreferenz und jeder Methodenname in der Anweisung als Member von objn behandelt, wenn dies möglich ist. Andernfalls wird die Referenz bzw. der Name als Member von objn1 interpretiert usw. Dieselbe Regel gilt für die Interpretation von objs selbst. Ist beispielsweise objn sowohl ein Member von obj1 als auch von obj2, wird es als obj2.objn interpretiert.
Da für eine with-Anweisung eine Variable oder ein Feld erforderlich ist, kann deren Verwendung mit Eigenschaften manchmal kompliziert sein. Variablen für eine with-Anweisung müssen per Referenz zur Verfügung stehen.
Beachten Sie bei der Verwendung von with Folgendes:
- with kann bei schreibgeschützten Eigenschaften nur zum Lesen verwendet werden. Der Versuch, ein Feld in dem betreffenden Record oder Objekt zu ändern, führt zu einem Compilierfehler.
- Auch wenn die Eigenschaft einen Schreibzugriff auf das Feld zulässt, können Sie deren Felder nicht mit with ändern.
Der folgende Code veranschaulicht das Problem bei der Verwendung der with-Anweisung für schreibgeschützte Eigenschaften, die einen Record bereitstellen. Angenommen, es gibt die folgende Klasse:
TShape = class
private
FCenter: TPoint;
public
property Center: TPoint read FCenter;
end;
TPoint ist ein folgendermaßen deklarierter Record:
TPoint = record
X, Y: Integer;
end;
Normalerweise ist die Eigenschaft Center schreibgeschützt und erlaubt das Ändern der Werte oder Felder des FCenter-Feldes nicht. In diesem Fall misslingt eine with-Anweisung wie die folgende mit einem Compilierfehler, weil Shape.Center keine Variable ist und nicht referenziert werden kann:
with Shape.Center do
begin
X := 100;
Y := 100;
end;
Eigenschaften für Lese- und Schreibzugriffe komplizieren die Verwendung der with-Anweisung. Die ursprüngliche TShape-Klasse wurde geändert, um einen Schreibzugriff auf das Feld FCenter zuzulassen:
TShape = class
private
FCenter: TPoint;
public
property Center: TPoint read FCenter '''write FCenter''';
end;
Obwohl die Eigenschaft Center nicht schreibgeschützt ist, wird derselbe Compilierfehler ausgegeben. Zur Lösung dieses Problems muss der folgende Code:
with Shape.Center do
begin
X := 100;
Y := 100;
end;
in diesen Code geändert werden:
{ Copy the value of Center to a local variable. }
TempPoint := Shape.Center;
with TempPoint do
begin
X := 100;
Y := 100;
end;
{ Set the value back. }
Shape.Center := TempPoint;
if-Anweisungen
Es gibt zwei Formen der if-Anweisung: if...then und if...then...else. Die Syntax für eine if...then-Anweisung lautet:
if expression then statement
Das Ergebnis von expression ist ein Wert vom Typ Boolean. statement wird nur ausgeführt, wenn expression True ergibt. Zum Beispiel:
if J <> 0 then Result := I / J;
Die Syntax für eine if...then...else-Anweisung lautet:
if expression then statement1 else statement2
Das Ergebnis von expression ist ein Wert vom Typ Boolean. Wenn expression den Wert True ergibt, wird statement1 ausgeführt, andernfalls statement2. Zum Beispiel:
if J = 0 then
Exit
else
Result := I / J;
Die then- und else-Klauseln enthalten jeweils nur eine Anweisung, die aber auch eine strukturierte Anweisung sein könnte. Zum Beispiel:
if J <> o then
begin
Result := I / J;
Count := Count + 1;
end
else if Count = Last then
Done := True
else
Exit;
Zwischen der then-Klausel und dem Wort else darf kein Strichpunkt stehen. Nach einer vollständigen if-Anweisung können Sie einen Strichpunkt verwenden, um sie von der nächsten Anweisung innerhalb des Blocks zu trennen. Zwischen der then- und der else-Klausel wird nur ein Leerzeichen oder ein Wagenrücklaufzeichen benötigt. Das Einfügen eines Strichpunkts vor dem Wort else (in einer if-Anweisung) ist ein häufiger Programmierfehler.
Auch verschachtelte if-Anweisungen bergen die Gefahr von Programmierfehlern. Und zwar deshalb, weil für bestimmte if-Anweisungen else-Klauseln existieren, für andere jedoch nicht. Ansonsten ist die Syntax für die beiden Konstrukte identisch. Wenn in einer Folge von Bedingungen weniger else-Klauseln als if-Anweisungen vorhanden sind, ist möglicherweise die Zuordnung der else-Klauseln zu den if-Anweisungen nicht eindeutig erkennbar. Die Anweisung
if expression1 then if expression2 then statement1 else statement2;
könnte demnach auf zwei Arten interpretiert werden:
if expression1 then [ if expression2 then statement1 else statement2 ];
if expression1 then [ if expression2 then statement1 ] else statement2;
Der Compiler verwendet aber immer die erste Interpretationsart. Dies bedeutet, dass die Anweisung:
if ... { expression1} then
if ... {expression2} then
... {statement1}
else
... {statement2}
gleichbedeutend ist mit:
if ... {expression1} then
begin
if ... {expression2} then
... {statement1}
else
... {statement2}
end;
Die Interpretation von verschachtelten Bedingungen beginnt immer bei der innersten Bedingung. Dabei wird jedes else dem letzten vorhergehenden if zugeordnet. Wenn der Compiler die zweite Interpretationsart verwenden soll, muss der Quelltext folgendermaßen aussehen:
if ...
{expression1}
then
begin
if ...
{expression2}
then
...
{statement1}
end
else
...
{statement2}
;
case-Anweisungen
Die case-Anweisung ist eine Alternative zur if-Anweisung, die aufgrund der besseren Lesbarkeit bei komplexen Verschachtelungen eingesetzt werden sollte. Die Syntax einer case-Anweisung lautet:
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
end
selectorExpression ist ein beliebiger Ausdruck mit einem ordinalen Typ, der kleiner als 32 Bit ist (String-Typen und ordinale Typen, die größer als 32 Bit sind, sind nicht zulässig). Für caseList kann Folgendes angegeben werden:
- Eine Zahl, eine deklarierte Konstante oder ein anderer Ausdruck, den der Compiler auswerten kann, ohne dazu das Programm selbst auszuführen. Dieser Wert muss ein ordinaler Typ sein, der mit dem Typ von selectorExpression kompatibel ist. 7, True, 4 + 5 * 3, 'A' und Integer('A') sind demnach als caseLists zulässig, Variablen und die meisten Funktionsaufrufe dagegen nicht. (Einige integrierte Funktionen wie Hi und Lo können in einer caseList enthalten sein. Siehe Deklarierte Konstanten.)
- Ein Teilbereich der Form Erster..Letzter, wobei Erster und Letzter dem obigen Kriterium entsprechen müssen und Erster kleiner oder gleich Letzter sein muss.
- Eine Liste der Form Element1, ..., Elementn, wobei jedes Element den obigen Kriterien entsprechen muss.
Jeder in einer caseList angegebene Wert muss innerhalb der case-Anweisung eindeutig sein. Teilbereiche und Listen dürfen sich nicht überschneiden. Eine case-Anweisung kann über eine abschließende else-Klausel verfügen:
case selectorExpression of
caseList1: statement1;
...
caselistn: statementn;
else
statements;
end
statements ist eine Folge von Anweisungen, die durch Strichpunkte voneinander getrennt sind. Bei der Ausführung einer case-Anweisung wird höchstens eine statement1 ...statementn-Anweisung ausgeführt. Dabei handelt es sich um genau diejenige, deren caseList-Wert mit dem von selectorExpression identisch ist. Ist kein entsprechender selectorExpression-Wert in caseList vorhanden, werden die Anweisungen in der else-Klausel (falls vorhanden) ausgeführt.
Die case-Anweisung
case I of
1..5: Caption := 'Low';
6..9: Caption := 'High';
0, 10..99: Caption := 'Out of range';
else
Caption := '';
end
ist mit der folgenden verschachtelten Anweisung identisch:
if I in [1..5] then
Caption := 'Low';
else if I in [6..10] then
Caption := 'High';
else if (I = 0) or (I in [10..99]) then
Caption := 'Out of range'
else
Caption := '';
Weitere Beispiele für case-Anweisungen
case MyColor of
Red: X := 1;
Green: X := 2;
Blue: X = 3;
Yellow, Orange, Black: X := 0;
end;
case Selection of
Done: Form1.Close;
Compute: calculateTotal(UnitCost, Quantity);
else
Beep;
end;
Schleifen
Schleifen ermöglichen die wiederholte Ausführung einer Anweisungsfolge. Eine Bedingung oder eine Variable bestimmt, wann die Ausführung angehalten wird. Delphi unterstützt drei Arten von Schleifen: repeat-, while- und for-Anweisungen.
Mit den Standardprozeduren Break und Continue können Sie den Ablauf einer repeat-, while- oder for-Anweisung steuern. Break beendet eine Schleife, Continue fährt mit der nächsten Iteration fort.
repeat-Anweisungen
Die Syntax einer repeat-Anweisung lautet:
repeat statement1; ...; statementn; until expression
Das Ergebnis von expression ist ein Wert vom Typ Boolean. (Die Angabe des letzten Strichpunkts vor until ist optional.) Alle repeat-Anweisungen werden der Reihe nach ausgeführt. Nach jedem Durchlauf wird expression ausgewertet. Liefert expression den Wert True, wird die repeat-Anweisung beendet. Da expression erst am Ende der ersten Iteration ausgewertet wird, wird die Anweisungsfolge mindestens einmal durchlaufen.
Hier einige Bespiele für repeat-Anweisungen:
repeat
K := I mod J;
I := J;
J := K;
until J = 0;
repeat
Write('Enter a value (0..9): ');
Readln(I);
until (I >= 0) and (I <= 9);
while-Anweisungen
Eine while-Anweisung ähnelt in vielerlei Hinsicht einer repeat-Anweisung, doch wird die Bedingung bereits vor der ersten Ausführung der Anweisungsfolge ausgewertet. Wenn das Ergebnis der Bedingung False ist, wird die Anweisungsfolge nicht ausgeführt.
Die Syntax einer while-Anweisung lautet:
while expression do statement
expression gibt einen booleschen Wert zurück. Bei statement kann es sich auch um eine Verbundanweisung handeln. Die while-Anweisung führt statement wiederholt aus und wertet vor jedem neuen Durchlauf expression aus. Solange expression den Wert True ergibt, wird die Ausführung fortgesetzt.
Hier einige Bespiele für while-Anweisungen:
while Data[I] <> X do I := I + 1;
while I > 0 do
begin
if Odd(I) then Z := Z * X;
I := I div 2;
X := Sqr(X);
end;
while not Eof(InputFile) do
begin
Readln(InputFile, Line);
Process(Line);
end;
for-Anweisungen
Im Gegensatz zu repeat- und while-Anweisungen wird bei for-Anweisungen angegeben, wie oft die Schleife durchlaufen werden soll. Die Syntax einer for-Anweisung lautet:
for counter := initialValue to finalValue do statement
oder:
for counter := initialValue downto finalValue do statement
wobei:
- counter eine lokale Variable ordinalen Typs ohne Qualifizierer ist. Sie wird in dem Block deklariert, in dem die for-Anweisung enthalten ist.
- initialValue und finalValue sind Ausdrücke, die zu counter zuweisungskompatibel sind.
- statement ist eine einfache oder eine strukturierte Anweisung, die den Wert von counter nicht ändert.
Die for-Anweisung weist counter den angegebenen initialValue zu und führt anschließend wiederholt statement aus. Abhängig von der verwendeten Syntax wird der Wert von counter bei jedem Durchlauf inkrementiert (for...to) oder dekrementiert (for...downto). Sobald der Wert von counter mit dem Wert von finalValue identisch ist, wird statement ein letztes Mal ausgeführt und anschließend die for-Anweisung beendet. statement wird also einmal für jeden Wert ausgeführt, der im Bereich von initialValue bis finalValue liegt. Ist initialValue mit finalValue identisch, wird statement genau einmal ausgeführt. Wenn initialValue in einer for...to-Anweisung größer als finalValue ist, wird statement nie ausgeführt. Dasselbe gilt für eine for...downto-Anweisung, in der der Anfangswert kleiner als finalValue ist. Nach der Beendigung der for-Anweisung ist der Wert von counter nicht definiert (vorausgesetzt, die Beendigung wurde nicht durch eine Break- oder Exit-Prozedur verursacht).
- Warnung: Die Iterationsvariable counter kann innerhalb der Schleife nicht verändert werden. Dies beinhaltet Zuweisungen und die Übergabe der Variable an einen var-Parameter einer Prozedur. Diese Operationen würden zu einer Compiler-Warnung führen.
Die Ausdrücke initialValue und finalValue werden zur Steuerung der Schleifenausführung nur einmal ausgewertet, und zwar vor Beginn der Schleife. Daher hat die for...to-Anweisung starke Ähnlichkeit mit dem folgenden while-Konstrukt:
begin
counter := initialValue;
while counter <= finalValue do
begin
... {statement};
counter := Succ(counter);
end;
end.
Im Unterschied zur for...to-Anweisung wird finalValue aber in der while-Schleife vor jedem Durchlauf erneut ausgewertet. Wenn finalValue ein komplexer Ausdruck ist, kann dies eine Verlangsamung der Ausführungsgeschwindigkeit zur Folge haben. Außerdem können sich von statement verursachte Änderungen an finalValue auf die Ausführung der Schleife auswirken.
Beispiele für for-Anweisungen
for I := 2 to 63 do
if Data[I] > Max then
Max := Data[I];
for I := ListBox1.Items.Count - 1 downto 0 do
ListBox1.Items[I] := UpperCase(ListBox1.Items[I]);
for I := 1 to 10 do
for J := 1 to 10 do
begin
X := 0;
for K := 1 to 10 do
X := X + Mat1[I,K] * Mat2[K,J];
Mat[I,J] := X;
end;
for C := Red to Blue do Check(C);
Iteration durch Container mit for-Anweisungen
Delphi unterstützt die for-Element-in-Kollektion-Iteration durch Container. Dabei werden die folgenden Iterationsmuster vom Compiler erkannt:
- for Element in ArrayAusdr do Anw;
- for Element in StringAusdr do Anw;
- for Element in SetAusdr do Anw;
- for Element in KollektionsAusdr do Anw;
- for Element in KollektionsAusdr do Anw;
Der Typ der Iterationsvariable Element muss mit dem Typ im Container übereinstimmen. Bei jedem Schleifendurchlauf enthält die Iterationsvariable das aktuelle Kollektionselement. Wie bei regulären for-Schleifen muss die Iterationsvariable in demselben Block wie die for-Anweisung deklariert werden.
- Warnung: Die Iterationsvariable kann innerhalb der Schleife nicht verändert werden. Dies beinhaltet Zuweisungen und die Übergabe der Variable an einen var-Parameter einer Prozedur. Diese Operationen würden zu einer Compiler-Warnung führen.
Array-Ausdrücke können sich auf ein- oder mehrdimensionale Arrays, Arrays mit fester Länge oder dynamische Arrays beziehen. Das Array wird in aufsteigender Reihenfolge durchlaufen, beginnend mit der unteren Array-Grenze bis zur Array-Größe minus eins. Der folgende Code zeigt Beispiele der Iteration durch eindimensionale, mehrdimensionale und dynamische Arrays:
type
TIntArray = array[0..9] of Integer;
TGenericIntArray = array of Integer;
var
IArray1: array[0..9] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IArray2: array[1..10] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IArray3: array[1..2] of TIntArray = ((11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
(21, 22, 23, 24, 25, 26, 27, 28, 29, 30));
MultiDimTemp: TIntArray;
DynArray: TGenericIntArray;
I: Integer;
begin
for I in IArray1 do
begin
{ Do something with I... }
end;
{ Indexing begins at lower array bound of 1. }
for I in IArray2 do
begin
{ Do something with I... }
end;
{ Iterating a multidimensional array }
for MultiDimTemp in IArray3 do // Indexing from 1..2
for I in MultiDimTemp do // Indexing from 0..9
begin
{ Do something with I... }
end;
{ Iterating over a dynamic array }
DynArray := TGenericIntArray.Create(1, 2, 3, 4);
for I in DynArray do
begin
{ Do something with I... }
end;
end.
Das folgende Beispiel zeigt die Iteration durch String-Ausdrücke:
var
C: Char;
S1, S2: String;
Counter: Integer;
OS1, OS2: ShortString;
AC: AnsiChar;
begin
S1 := 'Now is the time for all good men to come to the aid of their country.';
S2 := ''''''';
for C in S1 do
S2 := S2 + C;
if S1 = S2 then
Writeln('SUCCESS #1')
else
Writeln('FAIL #1');
OS1 := 'When in the course of human events it becomes necessary to dissolve...';
OS2 := ''''''';
for AC in OS1 do
OS2 := OS2 + AC;
if OS1 = OS2 then
Writeln('SUCCESS #2')
else
Writeln('FAIL #2');
end.
Das folgende Beispiel zeigt die Iteration durch Mengen-Ausdrücke:
type
TMyThing = (one, two, three);
TMySet = set of TMyThing;
TCharSet = set of Char;
var
MySet: TMySet;
MyThing: TMyThing;
CharSet: TCharSet;
C: Char;
begin
MySet := [one, two, three];
for MyThing in MySet do
begin
// Do something with MyThing...
end;
CharSet := [#0..#255];
for C in CharSet do
begin
// Do something with C...
end;
end.
Für die Verwendung von for-in-Schleifenkonstrukten bei Klassen muss die Klasse oder das Interface ein vorschriftsmäßiges Kollektionsmuster implementieren. Ein Typ, der Kollektionsmuster implementiert, muss die folgenden Attribute haben:
- Die Klasse oder das Interface muss eine als public deklarierte Instanzmethode namens GetEnumerator() enthalten. Die Methode GetEnumerator() muss einen Klassen-, Interface- oder Record-Typ zurückgeben.
- Die von GetEnumerator() zurückgegebene Klasse bzw. das Interface oder der Record muss eine als public deklarierte Instanzmethode namens MoveNext() enthalten. Die Methode MoveNext() muss einen booleschen Wert zurückgeben. Die for-in-Schleife ruft zuerst diese Methode auf, um sicherzustellen, dass der Container nicht leer ist.
- Die von GetEnumerator() zurückgegebene Klasse bzw. das Interface oder der Record muss eine als public deklarierte, schreibgeschützte Instanzeigenschaft namens Current enthalten. Der Typ der Eigenschaft Current muss mit dem in der Kollektion enthaltenen Typ übereinstimmen.
Der folgende Code zeigt die Iteration durch einen Container in Delphi.
type
TMyIntArray = array of Integer;
TMyContainerEnumerator = class;
TMyContainer = class
public
Values: TMyIntArray;
function GetEnumerator: TMyContainerEnumerator;
end;
TMyContainerEnumerator = class
Container : TMyContainer;
Index : Integer;
public
constructor Create(AContainer : TMyContainer);
function GetCurrent: Integer;
function MoveNext: Boolean;
property Current: Integer read GetCurrent;
end;
constructor TMyContainerEnumerator.Create(AContainer : TMyContainer);
begin
inherited Create;
Container := AContainer;
Index := - 1;
end;
function TMyContainerEnumerator.MoveNext: Boolean;
begin
Result := Index < High(Container.Values);
if Result then
Inc(Index);
end;
function TMyContainerEnumerator.GetCurrent: Integer;
begin
Result := Container.Values[Index];
end;
function TMyContainer.GetEnumerator: TMyContainerEnumerator;
begin
Result := TMyContainerEnumerator.Create(Self);
end;
var
MyContainer : TMyContainer;
I : Integer;
Counter : Integer;
begin
MyContainer := TMyContainer.Create;
MyContainer.Values := TMyIntArray.Create(100, 200, 300);
Counter := 0;
for I in MyContainer do
Inc(Counter, I);
Writeln('Counter = ', Counter, ' (should be 600)');
ReadLn;
end.
Als Teil der Enumeratortypen für Sammlungen definiert RAD Studio den Enumeratorstatus explizit, wenn die Iteration abgeschlossen ist: "Der Enumeratorstatus ist nicht gültig, nachdem MoveNext False zurückgegeben hat und der Enumerator freigegeben oder neu erstellt werden muss, und es sollte nicht mehr darauf zugegriffen werden."
Iteration durch Datenmengen mit for-Anweisungen
Delphi unterstützt die for-in-Syntaxkonstruktion für die Iteration durch Datenmengen. Der Compiler erkennt das folgende Iterationsmuster für Datenmengen:
- for Record in Dataset do Smth;
wobei Record
die TDataSet-API repräsentiert. Record kann problemlos mit Datenmenge gleichgesetzt werden.
Im folgenden Codefragment wird durch eine Datenmenge in Delphi iteriert. Der Beispielcode zeigt, wie die Werte der Spalte "Name" in ein Memo-Steuerelement ausgegeben werden.
var
ds: TDataSet;
//
FQuery1.SQL.Text := 'SELECT Name FROM Table1';
Memo1.Lines.Clear;
for ds in FDQuery1 do
Memo1.Lines.Add(ds.FieldByName('Name').AsString);
for-in
-Schleifen für dieselbe Datenmenge gleichzeitig ausführen müssen, verwenden Sie stattdessen die Methode TDataSet.View (siehe weiter unten in diesem Thema). In diesem Szenario ist in der for-in
-Schleife ein Record nicht unbedingt mit einer Datenmenge identisch.
Das folgende Codefragment zeigt, wie die Methode TDataSet.View zur Aufzählung einer Datenmenge verwendet wird.
var
ds: TDataSet;
//...
Memo1.Lines.Clear;
for ds in FDQuery1.View(dmAllowClone) do
Memo1.Lines.Add(ds.FieldByName('name').AsString);
Liste der unterstützten Klassen
Die folgenden Klassen und ihre Nachkommen unterstützen die for-in-Syntax:
- System.Classes.TList
- System.Classes.TCollection
- System.Classes.TStrings
- System.Classes.TInterfaceList
- System.Classes.TComponent
- Vcl.Menus.TMenuItem
- Vcl.ActnList.TCustomActionList
- Vcl.ComCtrls.TListItems
- Vcl.ComCtrls.TTreeNodes
- Vcl.ComCtrls.TToolBar
- Data.DB.TFields
- Data.DB.TDataSet
Blöcke und Gültigkeitsbereich
Deklarationen und Anweisungen werden in Blöcken zusammengefasst. Alle in einem Block deklarierten Labels und Bezeichner sind für diesen Block lokal, das heißt, der Block stellt den Gültigkeitsbereich für diese Labels und Bezeichner dar. Blöcke ermöglichen es, einem Bezeichner (z. B. einem Variablennamen) in den einzelnen Programmabschnitten eine unterschiedliche Bedeutung zu geben. Ein Block ist immer Teil der Deklaration eines Programms, einer Funktion oder einer Prozedur. Umgekehrt verfügt jede Programm-, Funktions- oder Prozedurdeklaration über mindestens einen Block.
Blockanweisungen
Ein Block besteht aus Deklarationen und einer Verbundanweisung. Alle Deklarationen müssen zusammen am Anfang des Blocks stehen. Ein Block ist folgendermaßen aufgebaut:
{declarations} begin {statements} end
Der Abschnitt declarations besteht aus Deklarationen von Variablen, Konstanten (einschließlich Ressourcenstrings), Typen, Prozeduren, Funktionen und Labels. In einem Programmblock können im declarations-Abschnitt auch eine oder mehrere exports-Klauseln enthalten sein (siehe Bibliotheken und Packages (Delphi).)
In einer Funktionsdeklaration wie der folgenden:
function UpperCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
...
end;
bildet die erste Zeile den Funktionskopf, die restliche Zeilen stellen den Block dar. Ch, L, Source und Dest sind lokale Variablen. Ihre Deklarationen sind nur im Funktionsblock von UpperCase gültig. Sie setzen in diesem Block (und nur hier) die Deklarationen aller Bezeichner gleichen Namens außer Kraft, die im program-Block bzw. im interface- oder implementation-Abschnitt einer Unit enthalten sind.
Gültigkeitsbereich
Ein Bezeichner, z. B. ein Variablen- oder Funktionsname, kann nur innerhalb des Gültigkeitsbereichs seiner Deklaration verwendet werden. Der Gültigkeitsbereich einer Deklaration hängt davon ab, wo die Deklaration stattfindet. Der Gültigkeitsbereich eines Bezeichners, der in der Deklaration eines Programms, einer Funktion oder einer Prozedur deklariert ist, beschränkt sich auf den Block, der die Deklaration enthält. Der Gültigkeitsbereich der im interface-Abschnitt einer Unit deklarierten Bezeichner erstreckt sich über alle Units und Programme, die diese Unit einbinden. Bezeichner mit eingeschränktem Gültigkeitsbereich (besonders solche, die in Funktionen und Prozeduren deklariert sind) werden lokale Bezeichner genannt. Bei Bezeichnern mit größerem Gültigkeitsbereich spricht man von globalen Bezeichnern.
Die folgende Tabelle gibt einen Überblick über die Regeln für den Gültigkeitsbereich von Bezeichnern.
Deklarationsort | Gültigkeitsbereich |
---|---|
Deklarationsabschnitt eines Programms, einer Funktion oder einer Prozedur |
Von der Deklaration bis zum Ende des aktuellen Blocks, einschließlich aller verschachtelten Blöcke. |
interface-Abschnitt einer Unit |
Von der Deklaration bis zum Ende der Unit sowie in allen anderen Units und Programmen, die diese Unit einbinden (siehe Programme und Units (Delphi).) |
implementation-Abschnitt einer Unit, aber nicht innerhalb des Blocks einer Funktion oder Prozedur |
Von der Deklaration bis zum Ende der Unit. Der Bezeichner ist für alle Funktionen und Prozeduren in der Unit verfügbar (einschließlich des initialization- und finalization-Abschnitts, falls vorhanden). |
Definition eines Record-Typs (d. h. der Bezeichner ist der Name eines Record-Feldes) |
Von der Deklaration bis zum Ende der Definition des Record-Typs (siehe "Records" in Stukturierte Typen (Delphi).) |
Definition einer Klasse (d. h. der Bezeichner ist der Name einer Datenfeldeigenschaft oder Methode der Klasse) |
Von der Deklaration bis zum Ende der Klassentypdefinition sowie in allen Nachkommen der Klasse und den Blöcken aller in der Klasse definierten Methoden und deren Nachkommen (siehe Klassen und Objekte (Delphi).) |
Namenskonflikte
Ein Block, der einen anderen einschließt, wird als äußerer Block bezeichnet, der eingeschlossene Block als innerer Block. Ein im äußeren Block deklarierter Bezeichner kann in einem inneren Block redeklariert werden. Die innere Deklaration setzt die äußere außer Kraft und bestimmt die Bedeutung, die der Bezeichner im inneren Block hat. Wenn Sie beispielsweise im interface-Abschnitt einer Unit eine Variable mit dem Namen MaxValue und dann in dieser Unit eine weitere Variable gleichen Namens in einem Funktionsblock deklarieren, beziehen sich alle nicht qualifizierten Vorkommen von MaxValue im Funktionsblock auf die lokale Deklaration. Ähnlich erzeugt eine Funktion, die innerhalb einer anderen Funktion deklariert wird, einen neuen, inneren Gültigkeitsbereich, in dem Bezeichner der äußeren Funktion lokal redeklariert werden können.
Die Verwendung mehrerer Units kompliziert die Definition des Gültigkeitsbereichs. Jede Unit in einer uses-Klausel ergibt einen neuen Gültigkeitsbereich, der die anderen verwendeten Units und das Programm oder die Unit einschließt, die die uses-Klausel enthält. Die erste Unit in einer uses-Klausel entspricht dem äußersten, die letzte Unit dem innersten Gültigkeitsbereich. Ist derselbe Bezeichner in den interface-Abschnitten mehrerer Units deklariert, bezieht sich eine nicht qualifizierte Referenz auf den Bezeichner auf die Deklaration im innersten Gültigkeitsbereich, d. h. auf die Deklaration in der Unit, in der die Referenz selbst auftritt. Ist der Bezeichner nicht in dieser Unit deklariert, bezieht sich die Referenz auf die letzte Unit in der uses-Klausel, die den Bezeichner deklariert.
Die Units System und SysInit werden automatisch von jedem Programm und von jeder Unit verwendet. Für die in System enthaltenen Deklarationen sowie für die vom Compiler automatisch erkannten vordefinierten Typen, Routinen und Konstanten gilt immer der äußerste Gültigkeitsbereich.
Mit qualifizierten Bezeichnern (siehe "Qualifizierte Bezeichner" in Grundlegende syntaktische Elemente (Delphi)) oder mit with-Anweisungen (siehe "with-Anweisungen" weiter oben) können diese Regeln für den Gültigkeitsbereich außer Kraft gesetzt und lokale Deklarationen umgangen werden.