Inhaltsverzeichnis

2.8 Kontrollstrukturen

Bisher haben Sie nur lineare Programme kennengelernt, das sind Programme, in denen die Anweisungen genau in der Reihenfolge ausgeführt werden, in der sie im Programmtext stehen. Eine entscheidende Fähigkeit von Computern ist jedoch gerade die Möglichkeit, Anweisungen zu wiederholen oder nur in Abhängigkeit von Bedingungen auszuführen, die während des Programmes geprüft werden. In BASIC wird diese Programmablaufkontrolle durch (bedingte) Sprünge im Programm erreicht (IF, GOTO, FOR ... NEXT, ON ... GOTO, GOSUB, ON ... GOSUB).

In diesem Abschnitt werden die entsprechenden Kontrollstrukturen in Pascal vorgestellt. Bedingungen und Wiederholungen werden durch sogenannte zusammengesetzte Anweisungen gebildet, die dem Programm eine Blockstruktur geben.

Diese Blockstruktur ist die Grundlage für ein fundamentales Prinzip der Strukturierten Programmierung: Der statische Programmtext zeigt bereits die dynamische Struktur (z.B. Schleifen) des Programmes. Ein Programm besteht dementsprechend aus Blöcken, die nur einen Eingang und einen Ausgang besitzen. Ein Zusammenstellen von Blöcken ist nur nach festen Regeln möglich, wobei immer wieder Blöcke mit nur einem Eingang und Ausgang gebildet werden.

Das mag Ihnen im Augenblick noch recht abstrakt anmuten, jedoch werden die Abbildungen in diesem Abschnitt den Zusammenhang zwischen der geschachtelten Einrückung des Programmtextes und der dadurch symbolisierten Schachtelung der Anweisungen im Programm verdeutlichen.

Ein Lerneffekt bei der Lektüre des zweiten Kapitels sollte sein, daß Sie durch Hinschauen die Struktur eines fremden Pascal-Programmes erkennen, ohne erst durch kleinliche Analyse jeder Anweisung zum Kern des Programmes vorzudringen.

Die elementaren Bausteine eines Programmes sind die einfachen Anweisungen. Beispiele für einfache Anweisungen kennen Sie bereits aus den Abschnitten 2.4 und 2.5. Es sind dies

  1. Zuweisungen von Werten eines Ausdruckes an eine Variable.
  2. Aufrufe von vordefinierten oder benutzerdefinierten Prozeduren. So ist z.B. die Anweisung WRITE(7) ein Aufruf der Standardprozedur WRITE mit dem Parameter 7.
  3. Sprunganweisungen mit dem Befehl GOTO innerhalb des Programmes.

Die Sprunganweisungen sind wahre Exoten in Pascalprogrammen, die in manchen Lehrbüchern (aus didaktischen Gründen) nicht einmal behandelt werden. Somit besteht ein Programm ausschließlich aus Zuweisungen und Prozeduraufrufen, die in geeigneter Reihenfolge aufgerufen werden. Diese »geeignete Reihenfolge« wird durch die sogenannten zusammengesetzten Anweisungen definiert, die nun jeweils mit Beispielen vorgestellt werden.

2.8.1 Anweisungsfolgen

Falls es nicht durch Kontrollstrukturen anders definiert wird, werden die Befehle eines Programmes strikt sequentiell ausgeführt. Wie Sie später erfahren werden, ist es oft nötig eine Folge von Anweisungen zu einer Einheit zusammenzufassen. Betrachten Sie zum Beispiel die folgende Anweisungsfolge:

BEGIN
  READLN(A, B);
  A:= SQRT(A*A + B*B);
  WRITELN('BETRAG =', A)
END

Sie besteht aus zwei Prozeduraufrufen (READLN und WRITELN) und einer Zuweisung. Diese Anweisungen werden durch Semikola voneinander getrennt und mit den reservierten Wortsymbolen BEGIN und END geklammert. Offensichtlich besteht also der Anweisungsteil eines Programmes aus einer einzigen Anweisungsfolge. Die folgende Abbildung zeigt die allgemeine Struktur einer Anweisungsfolge:

Bild 11: Struktur einer Anweisungsfolge

Die Klammerung mit BEGIN und END wird später dazu verwendet werden, um in zusammengesetzten Anweisungen, bei denen eine einzelne Anweisung erwartet wird, eine ganze Anweisungsfolge einzusetzen. Dies wird in der Abbildung durch den umfassenden Rahmen angedeutet.

Noch ein Wort zu den Semikola: Ein Semikolon trennt Anweisungen. Deshalb ist kein Semikolon vor dem abschließenden END erforderlich. Setzen Sie auch dort ein Semikolon,

BEGIN
  READLN(A, B);
  A:= SQRT(A*A + B*B);
  WRITELN('BETRAG =', A);
END

so erwartet der Compiler eine Anweisung. Um diesen Fall nicht als Fehler zu behandeln, gibt es in Pascal die Leeranweisung, die aus keinem Befehl besteht. Diese mysteriöse Anweisung tritt auch in den folgenden (korrekten) Anweisungsfolgen auf:

BEGIN END        (1 Leeranweisung)
BEGIN A:=B; END  (1 Leeranweisung)
BEGIN ; ; END    (3 Leeranweisungen)

Deshalb besitzt das Syntaxdiagramm ANWEISUNG in Anhang A auch einen »leeren Pfad«, auf dem kein Symbol steht.

2.8.2 Bedingte Anweisungen

Eine bedingte Anweisung (If-Anweisung) hat die folgende Form:

Bild 12: Struktur der If-Anweisung

Der Ausdruck muß ein Ergebnis vom Typ BOOLEAN liefern. Ist das Ergebnis TRUE, so wird die Anweisung nach dem Wortsymbol THEN ausgeführt. Ist das Ergebnis FALSE, so wird die Anweisung nach ELSE ausgeführt. Es gibt viele verschiedene Formen, die If- Anweisung im Quelltext zu formatieren. In diesem Buch wird durch- gängig folgendes Layout verwendet:

IF KONTO>=0 THEN
  WRITELN(KONTO :8, ' DM Guthaben')
ELSE
  WRITELN(KONTO :8, ' DM Schulden')

Wie Sie bereits wissen, ist dem Compiler nur die Symbolfolge und nicht ihre Formatierung wichtig, so daß er auch diese Einrückung akzeptiert:

PROGRAM MAXIMUM (INPUT,OUTPUT);
  VAR A,B,MAX: INTEGER;
BEGIN
  READLN(A,B);
  IF A>B THEN MAX:= A 
  ELSE MAX:= B;
  WRITELN('Das Maximum von ',A, ' und ',B,' ist ', MAX);
END.
Bei einer anderen Form der If-Anweisung ist kein ELSE-Zweig vorgesehen. Nur wenn die Bedingung des booleschen Ausdruckes zutrifft, wird die Anweisung nach THEN ausgeführt:

Bild 13: Einseitige Auswahl

Somit läßt sich das Maximum zweier Zahlen auch durch Austauschen bestimmen:

PROGRAM MAXSWAP (INPUT,OUTPUT);
  VAR A,B,H : INTEGER;
BEGIN
  READLN(A,B);
  IF A<B THEN
    BEGIN H:=A; A:=B; B:=H END;
  (* jetzt ist A>=B *)
  WRITELN(A, ' >= ', B);
END.

Dieses Beispiel zeigt auch, wie man statt einer einzelnen Anweisung eine ganze Anweisungsfolge durch eine Bedingung kontrolliert. Durch die Klammerung mit BEGIN und END erkennt der Compiler, welche Anweisungen der THEN-Teil umfaßt. Fehlen diese Klammern, so wird das Programm nicht korrekt übersetzt:

IF A>B THEN
  MAX:= A; MIN:= B
ELSE  FALSCH !
  MAX:= B; MIN:= A

Nach der oben angegebenen Syntax der If-Anweisung muß nach THEN und ELSE eine einzelne Anweisung folgen. Deshalb kann der Compiler das Wortsymbol ELSE nicht verarbeiten, was Sie spätestens an der Fehlermeldung

175 ERROR IN STATEMENT SEQUENCE

erkennen. Um die Struktur, die durch die Einrückung beschrieben wird, auch korrekt in Pascal zu formulieren, muß man also die Anweisungen MAX:=A; MIN:=B zu einer Anweisungsfolge zusammenfassen:

IF A>B THEN
  BEGIN MAX:= A; MIN:= B END
ELSE  RICHTIG !
  BEGIN MAX:= B; MIN:= A END

Auf einen weiteren Fallstrick müssen Sie noch achten: Hätten Sie vor dem Wortsymbol ELSE ein Semikolon gesetzt,

IF A>B THEN
  BEGIN MAX:= A; MIN:= B END;
ELSE  FALSCH !
  BEGIN MAX:= B; MIN:= A END

so würde der Compiler eine einseitige Auswahl wie in Bild 13 erkennen, und wiederum das ELSE mit der Fehlermeldung 175 markieren.

Am Beispiel der bedingten Anweisungen läßt sich bereits das Prinzip der Schachtelung von Anweisungen (Blöcken) erklären: Eine If-Anweisung ist eine zusammengesetzte Anweisung. Dadurch werden die Anweisungen nach THEN und ELSE zusammen mit der If-Abfrage zu einer einzigen Anweisung zusammgengefaßt. Das sollen auch die äußeren Rahmen in Bild 12 und Bild 13 verdeutlichen. Deshalb kann eine If-Anweisung selbst als Teil einer anderen If-Anweisung auftreten. Um die kleine Sammlung der Programme zur Bestimmung von Maxima und Minima zu komplettieren folgt als Beispiel die Berechnung des Maximums dreier ganzer Zahlen:

IF A>B THEN
  IF A>C THEN
    MAX:=A
  ELSE
    MAX:=C
ELSE
  IF B>C THEN
    MAX:=B
  ELSE
    MAX:=C

Bild 14: Geschachtelte Anweisungen

Im äußeren Block wird geprüft, ob A>B ist. Ist dies der Fall (A>B ist TRUE), so wird der erste geschachtelte Block ausgeführt, in dem der Vergleich A>C stattfindet. Je nach dem Ergebnis dieser zweiten Abfrage wird MAX:=A oder MAX:=C ausgeführt. War jedoch bereits im äußeren Block A<=C, so wird die andere geschachtelte If-Anweisung (B>C) ausgeführt.

Das obige Beispiel ist noch sehr übersichtlich, jedoch können Schachtelungen auftreten, bei denen die Vielzahl von IF, THEN und ELSE Verwirrung stiften kann:

PROGRAM UEBLESCHACHTEL(INPUT,OUTPUT);
  VAR X: INTEGER;
BEGIN
  READLN(X);
  IF X>0 THEN
    IF ODD(X) THEN
      WRITELN('X ist größer als 0 und ungerade')
  ELSE   FALSCH !
    WRITELN('X ist kleiner oder gleich 0');
END.

Sie sollten sich ruhig die Zeit nehmen, dieses Programm einzutippen und zu testen. Dann werden Sie nämlich feststellen, daß der Compiler ein Programm erzeugt, in dem der Text »X ist kleiner oder gleich 0« immer dann ausgegeben wird, falls X größer als Null und gerade ist.

Was ist also passiert? Offenbar wurde das ELSE dem »näherliegenden« IF ODD(X) zugeordnet. In Pascal gilt nämlich folgende Regel:

Im Zweifelsfall wird bei geschachtelten IF THEN ELSE Anweisungen jedes ELSE dem nächsten IF zugeordnet, zu dem noch kein ELSE gehört.

Bevor Sie sich jedoch Sorgen über diese Spezialregel machen, sei eine einfache Methode zur Vermeidung von Zweideutigkeiten vorgestellt: Es genügt nämlich, die gewünschte Zuordnung von IF und ELSE durch eine Klammerung mit BEGIN und END zu erzwingen:

PROGRAM GUTESCHACHTEL(INPUT,OUTPUT);
  VAR X: INTEGER;
BEGIN
  READLN(X);
  IF X>0 THEN
    BEGIN
      IF ODD(X) THEN
        WRITELN('X ist größer als 0 und ungerade')
    END
  ELSE  RICHTIG !
    WRITELN('X ist kleiner oder gleich 0');
END.

2.8.3 Fallunterscheidung

In manchen Fällen muß man in Abhängigkeit eines einzigen Wertes unterschiedliche Operationen vornehmen. Mit einer If-Anweisung erhält man dann Konstrukte der folgenden Form:

IF CH= '+' THEN ERG:= A + B
ELSE
  IF CH = '-' THEN ERG:= A - B
  ELSE
    IF CH = '*' THEN ERG:= A * B
    ELSE
      IF CH = '/' THEN ERG:= A / B
      ELSE
        WRITELN('Die Operation ', CH, ' ist nicht erlaubt');

Abgesehen davon, daß man bei einer solchen Einrückung bald den rechten Bildschirmrand erreichen wird, benötigen die vielen identischen Vergleiche unnötig viel Code. Für Anwendungen wie diese existiert in Pascal die Fallunterscheidung (Case-Anweisung):

Bild 15: Struktur der Case-Anweisung

Eine Fallunterscheidung wird folgendermaßen ausgeführt. Zunächst wird der Ausdruck ausgewertet. Er muß einen Wert eines skalaren Typs (z.B. CHAR, INTEGER, aber nicht REAL) liefern. Von den nachfolgenden Anweisungen wird nur diejenige ausgeführt, die den Wert des Ausdruckes in ihrer Konstantenliste enthält. Natürlich müssen die Fallmarken denselben Typ wie der Ausdruck besitzen. Kommt der Wert des Ausdruckes in keiner Fallmarke vor, so erfolgt ein Programmabbruch mit der Fehlermeldung:

NO LABEL IN CASE ERROR

In Pascal 2.0 ist die Syntax der Case-Anweisung dahingehend erweitert worden, daß am Ende der Fallmarken (vor dem abschließenden END) noch das Wortsymbol ELSE gefolgt von einer Anweisung stehen darf. Diese Anweisung wird ausgeführt, wenn der Wert des Ausdruckes mit keiner Fallmarke übereinstimmt. Das folgende Beispielprogramm zeigt eine Anwendung der Case-Anweisung zur Simulation eines einfachen Taschenrechners: Der Benutzer gibt zwei reelle Zahlen A und B sowie ein Operationszeichen ein. Das Ergebnis der Verknüpfung der Zahlen wird vom Computer ausgegeben:

Eingabe: * 30040
Ausgabe: 3.00000000E+02 * 4.00000000E+01 = 1.2000000E+04
PROGRAM COMPUTER (INPUT,OUTPUT);
  VAR OPERATION: CHAR;
      A, B, ERG: REAL;
      OK       : BOOLEAN;
BEGIN
  READLN(OPERATION, A, B);
  OK:= TRUE; (* Noch kein Fehler aufgetreten *)
  CASE OPERATION OF
   '.', '*' : BEGIN ERG:= A * B END;
   '/'      : BEGIN
                IF B = 0.0 THEN
                  BEGIN
                    OK:= FALSE;
                    WRITELN('Keine Division durch Null!');
                  END
                ELSE
                  ERG:= A / B;
              END;
   '+'      : ERG:= A + B;
   '-'  : ERG:= A - B
   ELSE   BEGIN
            OK:= FALSE;
            WRITELN('Operation ',OPERATION, ' unbekannt');
          END
  END; (* CASE *)
  IF OK THEN 
    WRITELN(A, ' ', OPERATION, ' ', B, ' = ', ERG);
END.

Dieses Programm zeigt auch eine Anwendung boolescher Variablen. Durch die Blockstruktur von Pascalprogrammen ist es nicht möglich, eine zusammengesetzte Anweisung »vorzeitig« zu verlassen und z.B. die Ausgabe des Ergebnisses zu »überspringen« (was wohl die übliche Realisierung in BASIC wäre). Stattdessen merkt man sich das Auftreten eines Fehlers, indem man die boolesche Variable OK auf FALSE (also nicht ok) setzt.

Wenn Sie sich noch an die grundsätzlichen Aussagen über die Blockstruktur in Pascal am Anfang des Kapitels 2.8 erinnern, so können Sie sich vielleicht jetzt etwas mehr darunter vorstellen, was es bedeutet, daß ein Block nur einen Eingang und einen Ausgang besitzen darf: Egal wie kompliziert sich der Programmfluß innerhalb der CASE-Anweisung verästelt, am Ende der Ausführung wird auf jeden Fall die Anweisung IF OK THEN ... ausgeführt.

Übrigens ist im Beispiel die Klammerung mit BEGIN und END bei den Fallmarken '*' und '.' nicht erforderlich, jedoch erleichertern solche redundanten Anweisungsfolgen oft die Erweiterung eines Programmes um zusätzliche Anweisungen.

Eine häufige Anwendung der CASE-Anweisung besteht in der Auswertung von Tastatureingaben. Das nächste Programmstück zeigt eine simulierte Cursorsteuerung: Mit den Cursortasten läßt sich ein Stern auf dem Bildschirm bewegen. Durch die Anwendung der Operation MOD, erscheint der Cursor beim Überschreiten des Bildschirmrandes auf der gegenüberliegenden Seite wieder. Durch die Betätigung der RETURN-Taste wird das Programm beendet. Im Vorgriff auf Abschnitt 2.8.5 wird bereits die REPEAT-Anweisung benutzt:

PROGRAM MOVECURSOR(INPUT,OUTPUT);
  CONST CRSRUP    = 145;(* Tastaturcodes:   *)
        CRSRDOWN  = 17;
        CRSRRIGHT = 29;
        CRSRLEFT  = 157;
        RETURN    = 13; 
        WIDTH     = 39; (* Bildschirmbreite *)
        HEIGHT    = 24; (* Bildschirmhoehe  *)
        (* Breite und Höhe für 40-Zeichen Bildschirm *)
  VAR   KEY: CHAR;
        X,Y: INTEGER;
BEGIN
  X:= WIDTH  DIV 2; (* Cursor auf Bildschirmmitte *)
  Y:= HEIGHT DIV 2;
  REPEAT
    DISPLAY(1,X,Y,'*');                 (* Cursor an  *)
    REPEAT GETKEY(KEY) UNTIL ORD(KEY)<>0;
    DISPLAY(1,X,Y,' ');                 (* Cursor aus *)
    CASE ORD(KEY) OF
      CRSRUP  :  IF Y=0 THEN Y:= HEIGHT
                 ELSE Y:= Y-1;
      CRSRDOWN:  Y:= (Y+1) MOD HEIGHT;
      CRSRLEFT:  IF X=0 THEN X:= WIDTH
                 ELSE X:= X-1;
      CRSRIGHT:  X:= (X+1) MOD WIDTH;
      RETURN  :  BEGIN (* Leeranweisung *) END
    ELSE WRITE(#7)                       (* piepsen *)
    END;
  UNTIL ORD(KEY)=RETURN; 
END.

Zum Schluß sei darauf hingewiesen, daß manche Compiler (nicht Pascal 2.0) Fallmarken aus einem zusammenhängenden Wertebereich erwarten. Sie würden für folgendes Beispiel einen sehr ungünstigen Code erzeugen:

CASE X OF
 2: BEGIN ... END;
 200 : BEGIN ... END;
 2000: BEGIN ... END
END

Inhaltsverzeichnis