GNU-Pascal in Beispielen: Kontrollstrukturen

zurück zu GNU-Pascal in Beispielen

Kontrollstrukturen

Bearbeiten

Bislang haben wir die Programmierung als eine lineare Abfolge von Befehlen kennen gelernt. Das Programm fing oben an und hörte unten auf, dazwischen war, mehr oder weniger unabhängig von der Benutzereingabe, der komplette Programmfluss statisch vorgegeben. Kontrollstrukturen sorgen dafür, dass je nach Gegebenheit ein anderer Code ausgeführt wird oder ein bestimmter Abschnitt des Programms nach bestimmten Regeln wiederholt wird.

Die if-Abfrage dient dazu, auf Eingaben, Zwischenergebnisse oder "Benutzerfehler" zu reagieren. Das folgende Beispiel ist ein modifiziertes "StringNachZahl"-Programm [1] mit Fehlerabfrage:

Programm: StringNachZahl2

Bearbeiten
program StringNachZahl2;

var
  Zahlentext: String (20);
  Zahl: Integer;
  Fehler: Integer;

begin
  Write ('Geben Sie eine ganze Zahl ein: ');
  ReadLn (Zahlentext);
  Val (Zahlentext, Zahl, Fehler);
  if Fehler = 0 then
    WriteLn ('Hervorragend!, Ihre Zahl lautet: ', Zahl);
  if Fehler <> 0 then
    begin
      WriteLn ('Fehler! Sie sollten doch eine ganze Zahl');
      WriteLn ('eingeben und nicht ''', Zahlentext, '''!');
    end
end.

Erklärung

Bearbeiten

Das Programm erwartet als Eingabe eine ganze Zahl, die es als String entgegennimmt und mit Hilfe von Val in eine ganze Zahl umwandelt. Wenn der Wert von Fehler bei dieser Umwandlung 0 ist, so wird die dazugehörige Anweisung WriteLn ('Hervorragend...'); ausgeführt. Den Bereich zwischen if und then kennen Sie bereits, dabei handelt es sich um einen Boolschen Ausdruck, eine so genannte Bedingung, die zu True oder False ausgewertet wird. Ergibt die Bedingung True, so wird die dazugehörige Anweisung ausgeführt.

Die zweite if-Abfrage überprüft, ob vielleicht doch ein Fehler aufgetreten ist. Wenn die Variable Fehler einen Wert hat, der verschieden von "0" ist, so wird der Anweisungsblock zwischen begin...end ausgeführt. Da hier zwei WriteLn-Anweisungen ausgeführt werden sollen, muss der Bereich auf diese Weise mit begin und end zusammengefasst werden. Bei einer einzelnen Anweisung ist es selbstverständlich, dass sie zur vorhergehenden if-Anweisung gehört. Bitte beachten Sie bei diesem Beispiel auch die Stellen, an denen Semikolons gesetzt werden: Immer nur zwischen zwei Anweisungen, wobei end keine Anweisung darstellt.

Ein Beispiel für zusammengesetzte Bedingungen ist eine Aufschrift auf einem Kinderkarussell: Nur Personen, die maximal 14 Jahre alt und kleiner als 1.2m sind dürfen dort mitfahren. Das Gegenteil dieser Bedingung lautet [2]: Personen, die älter als 14 Jahre sind oder mindestens 1.2m groß sind. Folgendes Beispiel verdeutlicht das:

Programm: Kinderkarussell1

Bearbeiten
program Kinderkarussell1;

var
  Alter, Groesse: Integer;

begin
  Write ('Wie alt bist Du?: ');
  ReadLn (Alter);
  Write ('Wie groß bist Du (in cm)?: ');
  ReadLn (Groesse);
  if (Alter <= 14) and (Groesse < 120) then
    WriteLn ('Du darfst auf das Kinderkarussell!');
  if (Alter > 14) or (Groesse >= 120) then
    WriteLn ('Du darfst nicht auf das Kinderkarussell.');
end.

Erklärung

Bearbeiten

Bei zusammengesetzten Bedingungen werden die einzelnen Bedingungen geklammert. Das liegt daran, dass, wie bei der "Punkt- vor Strichrechnung", die Operatoren and bzw. or eine höhere Priorität haben als <, <=, >, >=. Statt not (Alter <= 14)... in der zweiten if-Abfrage zu verwenden, wurde dieser Ausdruck fertig berechnet.

Der else-Ausdruck verzweigt in die Alternative zu if. Das obige Beispiel des Kinderkarussells sieht umgeschrieben unter Verwendung Verwendung von else folgendermaßen aus:

Programm: Kinderkarussell2

Bearbeiten
program Kinderkarussell2;

var
  Alter, Groesse: Integer;

begin
  Write ('Wie alt bist Du?: ');
  ReadLn (Alter);
  Write ('Wie groß bist Du (in cm)?: ');
  ReadLn (Groesse);
  if (Alter <= 14) and (Groesse < 120) then
    WriteLn ('Du darfst auf das Kinderkarussell!')
  else
    WriteLn ('Du darfst nicht auf das Kinderkarussell.')
end.

Erklärung

Bearbeiten

Else spart uns die zweite if-Abfrage aus dem oberen Beispiel. Jeder Grund, warum eine Person nicht auf das Kinderkarussell darf wird von diesem Ausdruck abgefangen. Das Programm wird dadurch lesbarer und es wird nur eine (zusammengesetzte) Bedingung ausgewertet, nicht zwei.


Mehrfachverzweigung mit if

Bearbeiten

Die hier vorgestellten Verzweigungen dürfen beliebig ineinander geschachtelt werden:

Programm: Kinderkarussell3

Bearbeiten
program Kinderkarussell3;

var
  Alter, Groesse: Integer;

begin
  Write ('Wie alt bist Du?: ');
  ReadLn (Alter);
  if Alter <= 14 then
    begin
      Write ('Wie groß bist Du (in cm)?: ');
      ReadLn (Groesse);
      if Groesse < 120 then
        WriteLn ('Du darfst auf das Kinderkarussell!')
      else
        WriteLn ('Du darfst nicht auf das Kinderkarussell.')
    end
  else
    WriteLn ('Du bist zu alt für das Kinderkarussell.')
end.

Erklärung

Bearbeiten

Dieses Beispiel stuft die zu einem Scheitern des Versuches führenden Gründe, auf das Kinderkarussell zu kommen, viel feiner ab. Außerdem wird bei einem zu hohen Alter keine weitere Abfrage benötigt. Falls das Alter passt, wird der gesamte innere begin...end-Block durchlaufen.

Eine andere Art der Mehrfachauswahl stellt das folgende Programm dar:

Programm: Kinderkarussell4

Bearbeiten
program Kinderkarussell4;

var
  Alter: Integer;

begin
  Write ('Wie alt bist Du?: ');
  ReadLn (Alter);
  if Alter <= 14 then
    WriteLn ('Du darfst auf das Kinderkarussell.')
  else if Alter >= 18 then
    begin
      WriteLn ('Wir suchen noch Mitarbeiter für das ');
      WriteLn ('Fahrgeschäft. Bitte melde Dich in der',
        ' Personalabteilung.')
    end
  else
    WriteLn ('Du darfst leider nicht auf das Kinderkarussell')
end.

Erklärung

Bearbeiten

Hier wird aus drei Bereichen ausgewählt: Die Person kann 14 Jahre oder jünger sein, 18 Jahre oder älter oder ein anderes Alter haben. Je nach Alter wird entsprechend verzweigt.


Mehrfachverzweigung mit case

Bearbeiten

Das letzte Kinderkarussell kann noch viel feiner differenzieren und benötigt dafür viel weniger Zeilen, ist damit übersichtlicher ohne den Komfort zu verlieren:

Programm: Kinderkarussell5

Bearbeiten
program Kinderkarussell5;

var
  Alter: Integer;

begin
  Write ('Wie alt bist Du?: ');
  ReadLn (Alter);
  case Alter of
         0: WriteLn ('Du bist bestimmt zu jung!');
     3..14: WriteLn ('Du darfst auf das Kinderkarussell');
    18..26: begin
              WriteLn ('Wir suchen noch Mitarbeiter für',
                ' das Fahrgeschäft. Bitte melde Dich in');
              WriteLn ('der Personalabteilung.')
            end
    otherwise
      WriteLn ('Du darfst leider nicht auf das Kinderkarussell')
  end
end.

Erklärung

Bearbeiten

Mit case Alter of beginnt der so genannte case-Block, der erst am gleich eingerückten end endet. Dazwischen befinden sich "case-Labels", die entweder sehr scharf (0:) oder innerhalb eines aufzählbaren und konstanten [3] Bereiches (3..14:) das Alter abfragen. Das Schlüsselwort otherwise dient dazu, alle Fälle abzufangen, für die kein "case-Label" vorgesehen ist. Dies entspricht dem else [4] in if-Abfragen.


Schleifen

Bearbeiten

Schleifen dienen dazu, sich wiederholenden Programmcode übersichtlich zu formulieren. Außerdem wird es mit der Verwendung von Schleifen möglich, einen Programmabschnitt beliebig oft zu wiederholen. Letztlich werden nur die Bedingungen notiert, unter denen sich der Code wiederholen muss und der Quellcode wird genau einmal geschrieben. Dieser sich wiederholende Programmteil wird Schleifenkörper oder Schleifenrumpf genannt. Es gibt drei verschiedenen Arten von Schleifen.

Schleifen mit for

Bearbeiten

Eine for-Schleife dient dazu, Programmcode eine bekannte Anzahl Mal ausführen zu lassen:

Programm: Schleife1
Bearbeiten
program Schleife1;

const
  Text = 'Hallo, Welt!';

var
  i: Integer;

begin
  for i := 1 to Length (Text) do
    WriteLn (i, '-ter Buchstabe ist ', Text[i], '.')
end.
Erklärung
Bearbeiten

Mit dieser Schleife wird jeder Buchstabe des Textes ausgegeben. Die Variable i ist dabei zu Beginn 1 und zum Schluss Length (Text), dazwischen hat sie jeden Wert in aufsteigender Reihenfolge genau einmal. Der Schleifenkörper gibt dabei den Wert der Variablen aus und den Buchstaben an der gegenwärtigen Position. Das Schüsselwort to in der oberen for-Anweisung sorgt dafür, dass vorwärts gezählt wird. Würden wir rückwärts zählen wollen, also den Text in umgekehrter Reihenfolge ausgeben wollen, so müssten wir das Schlüsselwort downto verwenden, also for i := Length (Text) downto 1 do.

Wie im Kapitel FF: set-section über Mengen angemerkt, folgt hier das Beispiel zum Thema Durchlaufen von Mengen:

Programm: Menge2
Bearbeiten
program Menge2;

var
  MeineMenge: set of Char;
  Element: Char;

begin
  MeineMenge := ['a'..'c', 'B', 'C'];
  for Element in MeineMenge do
    begin
      Write ('Meine Menge enthält das Element: ', Element);
      WriteLn (' mit der Ordnung: ', Ord (Element))
    end
end.
Erklärung
Bearbeiten

Da die Mächtigkeit [5] der Menge zu Beginn der Schleife feststeht, kann die Menge auf diese Weise durchlaufen werden. Element enthält zu Beginn des Schleifendurchlaufs allerdings den Wert 'B', nicht etwa 'a', wie es die Mengeninitialisierung nahelegt, weil es der Wert mit der kleinsten Ordnung ist, wie sich leicht bei einem Programmlauf feststellen lässt:

Meine Menge enthält das Element: B mit der Ordnung: 66
Meine Menge enthält das Element: C mit der Ordnung: 67
Meine Menge enthält das Element: a mit der Ordnung: 97
Meine Menge enthält das Element: b mit der Ordnung: 98
Meine Menge enthält das Element: c mit der Ordnung: 99

Der Inhalt von Variablen vom Typ einer Aufzählung (vergl. FF aufz-section) lässt sich nicht direkt auf den Bildschirm schreiben. Wie sollte auch der Computer mit einem Befehl umgehen, der "Schreibe Rot auf den Bildschirm" heißt? Meint der Anwender einen roten Apfel oder das Wort "Rot"? Hierbei hilft die case-Anweisung:

Programm: Aufzaehlung2
Bearbeiten
program Aufzaehlung2;

var
  Farbe: (Rot, Gelb, Blau);

begin
  for Farbe := Rot to Blau do
    case Farbe of
      Rot:   WriteLn ('Rot');
      Gelb:  WriteLn ('Gelb');
      Blau:  WriteLn ('Blau')
    end
end.
Erklärung
Bearbeiten

Dieses Programm schreibt nacheinander die Worte "Rot", "Gelb" und "Blau" auf den Bildschirm. Schleifen lassen sich vorher beenden, hierzu dient der Ausdruck Break. Folgendes Programm zeigt dessen Arbeitsweise:

Programm: Schleife2
Bearbeiten
program Schleife2;

var
  Anzahl: Integer;

begin
  for Anzahl := 1 to 10 do
    begin
      WriteLn (Anzahl, ' ist da.');
      if Anzahl = 3 then
        Break
    end;
end.
Erklärung
Bearbeiten

Es werden nur die ersten Drei WriteLn-Anweisungen ausgeführt. Nach der dritten Anweisung wird Break aufgerufen wird. Die Schleife wird damit unverzüglich verlassen.

Ebenso wie es eine Anweisung für das Verlassen von Schleifen gibt, gibt es eine Anweisung, um wieder in den Programmkopf zurückzukehren ohne den Rest des Schleifenkörpers auszuführen:

Programm: Schleife3
Bearbeiten
program Schleife3;

var
  Anzahl: Integer;

begin
  for Anzahl := 1 to 10 do
    begin
      if Anzahl Mod 2 = 0 then
        Continue;
      WriteLn (Anzahl, ' ist da.')
    end;
end.
Erklärung
Bearbeiten

Wenn die Integer-Division von Anzahl und 2 den Rest 0 ergibt, was bei allen geraden Zahlen der Fall ist, dann sorgt Continue dafür, dass sofort in den Schleifenkopf zurückgesprungen wird ohne die benachbarte WriteLn-Anweisung auszuführen. Dadurch werden nur ungerade Zahlen ausgegeben.

Schleifen mit while

Bearbeiten

Mit dem Schlüsselwort While wird die abweisende Schleife gebildet. Das Besondere an abweisenden Schleifen ist, dass schon in der Schleifenbedingung überprüft wird, ob diese Schleife überhaupt durchlaufen wird. Damit wird diese Schleife gegebenenfalls nie durchlaufen. Den Schleifenkörper auszuführen kann while also abweisen.

Programm: Wuerfelglueck1
Bearbeiten
program Wuerfelglueck1;

const
  ZufallGrenze = 10;
  WunschZahl = 5;

var
  Anzahl: Integer = 1;
  Zufall: Integer;

begin
  Zufall := Random (ZufallGrenze);
  WriteLn ('ZufallsZahl = ', Zufall);

  while Zufall <> WunschZahl do
    begin
      Zufall := Random (ZufallGrenze);
      WriteLn ('ZufallsZahl = ', Zufall);
      Inc (Anzahl)
    end;

  WriteLn ('Gefunden nach ', Anzahl, ' Versuchen')
end.
Erklärung
Bearbeiten

Zuerst wird eine Zufallszahl im Bereich von 0 bis ZufallGrenze - 1 gezogen. Wenn die Zufallszahl nicht der Wunschzahl entspricht, wird der Schleifenkörper durchlaufen und die Anzahl der Versuche, eine passende Zufallszahl zu ziehen wird protokolliert. Dies geschieht mit Hilfe der Prozedur Inc. Diese Prozedur ist gleichbedeutend mit der Anweisung Anzahl := Anzahl + 1, was bedeutet, die Variable Anzahl um eins zu erhöhen. Am Ende des Programms wird die Anzahl der Versuche ausgegeben. Das "Gegenteil" von Inc ist Dec, was die Variable um eins erniedrigt.

Schleifen mit repeat...until

Bearbeiten

Repeat...until ist eine nichtabweisende Schleife. Nichtabweisende Schleifen werden mindestens einmal durchlaufen, weil die Abbruchbedingung am Ende des Schleifenkörpers liegt. Der Schleifenkörper braucht im Falle von mehreren Anweisungen nicht von begin und end umschlossen zu werden, da repeat und until selbst einen Block bilden. Ein positiver Nebeneffekt bei der Benutzung dieser Schleife ist die geringere Einrückung. Obiges Beispiel sieht umgeschrieben so aus:

Programm: Wuerfelglueck2
Bearbeiten
program Wuerfelglueck2;

const
  ZufallGrenze = 10;
  WunschZahl = 5;

var
  Anzahl: Integer = 0;
  Zufall: Integer;

begin
  repeat
    Zufall := Random (ZufallGrenze);
    WriteLn ('ZufallsZahl = ', Zufall);
    Inc (Anzahl)
  until Zufall = WunschZahl;
  WriteLn ('Gefunden nach ', Anzahl, ' Versuchen')
end.
Erklärung
Bearbeiten

Im Gegensatz zu Wuerfelglueck1 wird die Variable Anzahl hier mit 0 initialisiert. Der Schleifenkörper, ab repeat wird mindestens einmal durchlaufen, dabei wird der Zufallswert ermittelt und die Anzahl der Versuche auf "1" gesetzt. Der Schleifenkopf soll durchlaufen werden, bis Zufall = WunschZahl ist. Dann wird, wie im entsprechenden while-Beispiel, die Anzahl der Versuche ausgegeben.


Programmierbeispiel: Zahlenraten

Bearbeiten

Das folgende Beispiel implementiert mit den uns bekannten Mitteln das Spiel "Zahlenraten", bei dem es darum geht, eine Zufallszahl zwischen 1 und 100 zu erraten. Die einzigen Hilfestellungen sind die Hinweise "Zu groß" und "Zu klein":

Programm: Zahlenraten

Bearbeiten
program Zahlenraten;

const
  ObereGrenze = 100;

var
  Anzahl: Integer = 0;
  Zufallszahl, RateZahl: Integer;

begin
  Randomize;
  Zufallszahl := Random (ObereGrenze) + 1;
  Write ('Ich denke mir eine Zufallszahl zwischen 1 und ');
  WriteLn (ObereGrenze, ' aus, die Sie raten müssen.');
  repeat
    Write ('RateZahl: ');
    ReadLn (RateZahl);
    Inc (Anzahl);
    if RateZahl > ZufallsZahl then
      WriteLn ('Zu groß!')
    else if RateZahl < ZufallsZahl then
      WriteLn ('Zu klein!')
  until Zufallszahl = RateZahl;
  WriteLn ('Gefunden nach ', Anzahl, ' Versuchen')
end.

Erklärung

Bearbeiten

Mit Random wird eine Zufallszahl erzeugt, die es zu raten gilt. Vom Benutzer wird ein Rateversuch eingegeben, die Anzahl der Versuche, die richtige Zahl zu finden wird protokolliert (Inc (Anzahl)). Je nachdem, ob die geratene Zahl zu groß oder zu klein ist, wird ein Hinweis ausgegeben. Der Schleifenkörper wird so lange durchlaufen, wie die Zufallszahl und die Ratezahl nicht übereinstimmen.


Programmierbeispiel: Palindrom

Bearbeiten

Ein Palindrom ist ein Wort [6], welches von hinten und von vorne gleich gelesen werden kann. Beispiele für solche Worte sind "OTTO" und "UHU". Dieses Programm testet ein Wort darauf, ob es ein Palindrom ist:

Programm: Palindrom

Bearbeiten
program Palindrom;

var
  Wort: String (100);
  i, Laenge: Integer;
  IstPalindrom: Boolean = True;

begin
  WriteLn ('Das Programm untersucht, ob ein eingegebenes');
  WriteLn ('Wort ein Palindrom ist.');
  Write ('Geben Sie ein Wort ein: ');
  ReadLn (Wort);
  Laenge := Length (Wort);
  for i := 1 to Laenge div 2 do
    begin
      if Wort[i] <> Wort[Laenge - i + 1] then
        begin
          IstPalindrom := False;
          Break
        end
    end;
  if IstPalindrom then
    WriteLn ('''', Wort, ''' ist ein Palindrom.')
  else
    WriteLn ('''', Wort, ''' ist kein Palindrom.')
end.

Erklärung

Bearbeiten

Vom eingegebene Wort wird die Länge benötigt, da der Algorithmus überprüft, ob links und rechts der Mitte des Wortes die Buchstaben übereinstimmen.

Der Algorithmus sieht wie folgt aus: Wenn der erste Buchstabe und der Letzte nicht übereinstimmen, so ist das Wort kein Palindrom. Wenn der Zweite und der vorletzte Buchstabe nicht übereinstimmen, so ist das Wort ebenfalls kein Palindrom..., sonst ist es ein Palindrom. Die Bedingung Wort[i] <> Wort[Laenge - i + 1] überprüft genau diese Bedingung. i ist dabei der Zähler, der bis maximal zur Mitte des Wortes reicht [7].

Wort[i] und Wort[Laenge - i + 1] sind spiegelbildliche Buchstabenpositionen innerhalb des Wortes. Die +1 kommt daher, dass der Anfangsindex des Wortes Eins ist. Wenn zu Beginn auf das letzte Zeichen des Wortes zugegriffen werden soll, so ist damit Wort[Laenge] gemeint, nicht etwa Wort[Laenge - i] (wobei i = 1 ist). Die Addition von 1 führt genau zu dieser Korrektur.

Anmerkungen

Bearbeiten
  1. vgl. Kapitel Variablen und Typen
  2. Nach den Morganschen Regeln: not (A and B) = not A or not B.
  3. Als "case-Labels" dürfen keine Variablen verwendet werden.
  4. tatsächlich ist hier auch else erlaubt. Die Wahl des Wortes ist Geschmackssache.
  5. Synonym für Anzahl der Elemente innerhalb der Menge.
  6. Ein Palindrom kann auch ein Satz sein. In diesem Beispiel würden die Stellungen der Leerzeichen mitberücksichtigt werden, so dass bekannte palindrome Sätze nicht funktionieren.
  7. Bei ungeraden Wortlängen bis ein Buchstaben vor der Mitte, da einbuchstabige Worte palindrom sind. Bei geraden Wortlängen gibt es keinen mittleren Buchstaben oder derer zwei, wie man will