Deklarationen und Anweisungen (Delphi)

Aus RAD Studio
Wechseln zu: Navigation, Suche

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).

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
 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.

Iteration durch Datenmenge 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);

Hinweis: In die Datenmengenaufzählung kann nicht erneut eingetreten werden. Das heißt, dass Sie für jede Datenmenge jeweils nur eine Aufzählung verwenden können. Wenn Sie mehrere 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:

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.

Siehe auch