Programmieren in C++: Einführung


Kapitel 4: Kontrollstrukturen


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


4.1 Verzweigungen

Unterschiedlichen Code ausführen

Verzweigungen ermöglichen in Abhängigkeit einer Bedingung eine unterschiedliche Code-Ausführung. Verzweigungen werden in C++ mit Hilfe des Schlüsselwortes if erstellt. Betrachten Sie folgendes Beispiel.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl ein: " << std::flush; 
  std::cin >> i; 
  if (i < 100) 
  { 
    std::cout << "Sie haben eine Zahl kleiner als 100 eingegeben." << std::endl; 
  } 
} 

Der Anwender wird aufgefordert, eine Zahl einzugeben. Diese wird über die Standardeingabe eingelesen und in der Variablen i gespeichert. Nun kommt die if-Kontrollstruktur ins Spiel. Hinter dem Schlüsselwort if wird innerhalb einer runden Klammer eine Bedingung überprüft. Derartige Überprüfungen finden normalerweise mit Hilfe von Vergleichsoperatoren statt. In diesem Fall wird überprüft, ob die in i gespeicherte Zahl kleiner als 100 ist. Ist dies der Fall - wird also von der Bedingung true als Ergebnis zurückgegeben - so wird der Anweisungsblock zwischen den geschweiften Klammern hinter dem if-Schlüsselwort ausgeführt.

Der grundsätzliche Aufbau einer if-Kontrollstruktur sieht wie folgt aus.

if (BEDINGUNG) 
{ 
  ANWEISUNGSBLOCK 
} 

Die Anweisungen im Anweisungsblock werden genau dann und genau einmal ausgeführt, wenn die zu überprüfende Bedingung true ergibt. Ergibt die zu überprüfende Bedingung false, wird der Anweisungsblock übersprungen, und die Programmausführung setzt hinter dem Anweisungsblock fort.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl ein: " << std::flush; 
  std::cin >> i; 
  if (i < 100) 
  { 
    std::cout << "Sie haben eine Zahl kleiner als 100 eingegeben." << std::endl; 
  } 
  else 
  { 
    std::cout << "Sie haben eine Zahl groesser gleich 100 eingegeben." << std::endl; 
  } 
} 

Eine if-Kontrollstruktur lässt sich zu einer if-else-Kontrollstruktur ausbauen. Hinter dem if-Anweisungsblock wird ein Schlüsselwort else gesetzt, dem wiederum ein Anweisungsblock in geschweiften Klammern folgt. Der Anweisungsblock hinter else wird genau dann und genau einmal ausgeführt, wenn die zu überprüfende Bedingung hinter dem vorherigen if im Ergebnis false ergab.

Der schematische Aufbau einer if-else-Kontrollstruktur sieht demnach wie folgt aus.

if (BEDINGUNG) 
{ 
  ANWEISUNGSBLOCK 
} 
else 
{ 
  ANWEISUNGSBLOCK 
} 

Der else-Block ist, wie Sie nun wissen, optional und kann jederzeit weggelassen werden.

Es ist möglich, if-else-Kontrollstrukturen zu verschachteln. Betrachten Sie folgendes Beispiel.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl ein: " << std::flush; 
  std::cin >> i; 
  if (i < 100) 
  { 
    std::cout << "Sie haben eine Zahl kleiner als 100 eingegeben." << std::endl; 
  } 
  else 
  { 
    if (i < 200) 
    { 
      std::cout << "Sie haben eine Zahl kleiner als 200 eingegeben." << std::endl; 
    } 
    else 
    { 
      std::cout << "Sie haben eine Zahl groesser gleich 200 eingegeben." << std::endl; 
    } 
  } 
} 

Innerhalb des else-Anweisungsblockes befindet sich eine zweite if-else-Kontrollstruktur. Diese überprüft, ob die eingegebene Zahl in i kleiner als 200 ist und zeigt, wenn dies der Fall ist, eine entsprechende Meldung auf dem Bildschirm an. Andernfalls wird die Meldung ausgegeben, dass die eingegebene Zahl größer oder gleich 200 ist.

Die Verschachtelung von if-else-Kontrollstrukturen kann recht häufig notwendig sein. Eine kompaktere Schreibweise des Codes ist daher vorzuziehen.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl ein: " << std::flush; 
  std::cin >> i; 
  if (i < 100) 
  { 
    std::cout << "Sie haben eine Zahl kleiner als 100 eingegeben." << std::endl; 
  } 
  else if (i < 200) 
  { 
    std::cout << "Sie haben eine Zahl kleiner als 200 eingegeben." << std::endl; 
  } 
  else 
  { 
    std::cout << "Sie haben eine Zahl groesser gleich 200 eingegeben." << std::endl; 
  } 
} 

Das Beispiel funktioniert genauso wie vorher. Es wird zuerst überprüft, ob die eingegebene Zahl kleiner als 100 ist. Wenn nicht, wird überprüft, ob die eingegebene Zahl kleiner als 200 ist. Ist dies auch nicht der Fall, werden die Anweisungen hinter else ausgeführt.

Innerhalb einer Verzweigung können beliebig viele else if-Überprüfungen stattfinden. Beginnen muss eine Verzweigung jedoch immer mit if. Der else-Block ist wie bereits erwähnt optional und kann jederzeit weggelassen werden. Wenn er jedoch verwendet wird, darf hinter else keine nochmalige Überprüfung einer Bedingung mit else if erfolgen.

Wenn der Anweisungsblock nur aus einer einzigen Anweisung besteht, können Sie die geschweiften Klammern weglassen. Gerade Anfängern in C++ wird jedoch empfohlen, immer die geschweiften Klammern zu setzen, um nicht versehentlich Anweisungen außerhalb eines if-, else if- oder else-Anweisungsblockes zu schreiben.

Eine besondere Form der if-else-Kontrollstruktur ist die switch-case-Anweisung.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl von 1 bis 3 ein: " << std::flush; 
  std::cin >> i; 
  switch (i) 
  { 
    case 1: 
    { 
      std::cout << "Sie haben 1 eingegeben." << std::endl; 
      break; 
    } 
    case 2: 
    { 
      std::cout << "Sie haben 2 eingegeben." << std::endl; 
      break; 
    } 
    case 3: 
    { 
      std::cout << "Sie haben 3 eingegeben." << std::endl; 
      break; 
    } 
    default: 
    { 
      std::cout << "Sie haben keine Zahl von 1 bis 3 eingegeben." << std::endl; 
      break; 
    } 
  } 
} 

Der switch-case-Anweisung wird hinter dem Schlüsselwort switch in runden Klammern eine Variable übergeben. Diese Variable - und das ist eine große Beschränkung von switch-case-Anweisungen - muss von einem intrinsischen Datentyp sein. Es darf sich hierbei auch um kein Array handeln.

Hinter der runden Klammer, die tatsächlich nur eine Variable enthält und keine Bedingung, folgt der Anweisungsblock, der wie gewöhnlich zwischen zwei geschweiften Klammern steht. Innerhalb dieses Anweisungsblocks stehen nun mehrere case-Anweisungen. Hinter dem Schlüsselwort case wird ein Wert angegeben, auf den die Variable überprüft werden soll, die in Klammern hinter switch angegeben ist. Hinter dem Wert folgt ein Doppelpunkt.

Indem mehrere case-Schlüsselwörter mit unterschiedlichen Werten innerhalb des Anweisungsblocks der switch-case-Anweisung angegeben werden, kann die Variable in Klammern auf mehrere Werte hin überprüft werden. Letztendlich entspricht dies nichts anderem als einer if-else-Kontrollstruktur, die in mehreren else if-Anweisungen eine Variable auf Gleichheit mit unterschiedlichen Werten überprüft.

Hinter dem Doppelpunkt einer case-Anweisung folgt wiederum ein Anweisungsblock. Es können die geschweiften Klammern in diesem Fall auch weggelassen und die Anweisungen direkt untereinander geschrieben werden. Der Anweisungsblock wird nämlich nicht durch eine geschlossene geschweifte Klammer beendet, sondern erst durch das Schlüsselwort break. Wenn Sie das Schlüsselwort break vergessen, wird am Ende eines Anweisungsblockes hinter einem case der darauffolgende Anweisungsblock hinter dem nächsten case durchlaufen. Dies kann ab und zu sinnvoll sein, handelt sich aber oft um einen Programmierfehler.

Trifft die Code-Ausführung auf break, wird die switch-case-Kontrollstruktur abgebrochen, und die Programmausführung setzt hinter der Kontrollstruktur fort.

Innerhalb einer switch-case-Kontrollstruktur beginnen normalerweise alle Zweige mit einem case-Schlüsselwort. Sie können jedoch auch mit dem Schlüsselwort default einen eigenen Abschnitt einleiten. Hinter default wird direkt der Doppelpunkt angegeben. Der Anweisungsblock hinter default wird immer dann ausgeführt, wenn kein case zutreffend war. default entspricht also dem else innerhalb einer if-else-Kontrollstruktur. Ebenso wie der else-Anweisungsblock ist auch der default-Anweisungsblock optional.

Möchten Sie einen Anweisungsblock ausführen, wenn verschiedene Werte zutreffen, können Sie folgendes schreiben.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl von 1 bis 3 ein: " << std::flush; 
  std::cin >> i; 
  switch (i) 
  { 
    case 1: 
    case 3: 
    { 
      std::cout << "Sie haben eine ungerade Zahl eingegeben." << std::endl; 
      break; 
    } 
    case 2: 
    { 
      std::cout << "Sie haben eine gerade Zahl eingegeben." << std::endl; 
      break; 
    } 
    default: 
    { 
      std::cout << "Sie haben keine Zahl von 1 bis 3 eingegeben." << std::endl; 
      break; 
    } 
  } 
} 

Die Meldung, dass eine ungerade Zahl eingegeben wurde, wird nun ausgegeben, wenn die Zahl 1 oder die Zahl 3 vom Anwender eingegeben wurde. Indem hinter case 1: nämlich kein break angegeben wird, wird der darauffolgende Anweisungsblock hinter case 3: ausgeführt - bis die Programmausführung auf ein break trifft und die Kontrollstruktur abbricht.


4.2 Schleifen

Code mehrmals ausführen

Während mit Hilfe einer if-Kontrollstruktur unterschiedlicher Code genau einmal ausgeführt werden kann, kann mit Schleifen Code in Abhängigkeit einer Bedingung wiederholt ausgeführt werden. C++ stellt verschiedene Schleifentypen zur Verfügung.

#include <iostream> 

int main() 
{ 
  int i; 

  std::cout << "Geben Sie eine Zahl groesser gleich 100 ein: " << std::flush; 
  std::cin >> i; 
  while (i < 100) 
  { 
    std::cout << "Geben Sie eine Zahl groesser gleich 100 ein: " << std::flush; 
    std::cin >> i; 
  } 
} 

Eine while-Schleife ist ähnlich wie eine if-Kontrollstruktur aufgebaut: Zuerst kommt das Schlüsselwort, in diesem Fall while, danach wird in runden Klammern eine Bedingung überprüft, dahinter folgt zwischen geschweiften Klammern ein Anweisungsblock. Abhängig von der Bedingung wird der Anweisungsblock ausgeführt, oder aber die Code-Ausführung setzt hinter dem Anweisungsblock fort.

Im Gegensatz zum if wird jedoch nach einem Durchlauf des Anweisungsblocks die Bedingung in runden Klammern erneut überprüft. Ist sie wieder true, wird der Anweisungsblock nochmal durchlaufen. Auch nach diesem Durchlauf findet erneut eine Überprüfung der Bedingung statt mit eventuell anschließendem erneuten Durchlauf des Anweisungsblocks. Dieses Spielchen wiederholt sich solange, bis die zu überprüfende Bedingung false ergibt. Erst dann wird der Anweisungsblock übersprungen und das Programm danach fortgesetzt.

#include <iostream> 

int main() 
{ 
  int i; 

  do 
  { 
    std::cout << "Geben Sie eine Zahl groesser gleich 100 ein: " << std::flush; 
    std::cin >> i; 
  } 
  while (i < 100); 
} 

Wenn Sie sich das Beispiel eben genau angesehen haben, haben Sie festgestellt, dass zwei Zeilen zweimal angegeben waren - einmal vor der Schleife, einmal innerhalb der Schleife. Wenn Sie anstatt einer while-Schleife eine do-while-Schleife verwenden, können Sie wie im obigen Beispiel Ihren Code vereinfachen.

Im Gegensatz zu einer while-Schleife findet bei einer do-while-Schleife die Überprüfung der Bedingung am Ende eines Schleifendurchgangs statt. Das bedeutet, dass der Anweisungsblock der Schleife auf alle Fälle einmal ausgeführt wird. Erst nach dieser einmaligen Ausführung wird entschieden, ob die Schleife nochmal durchlaufen werden muss. Während in einer while-Schleife zuerst die Bedingung und dann die Schleife ausgeführt wird, wird in einer do-while-Schleife zuerst die Schleife durchlaufen und dann die Bedingung überprüft.

Beachten Sie, dass in einer do-while-Schleife hinter der Überprüfung der Bedingung ein Semikolon gesetzt werden muss.

#include <iostream> 

int main() 
{ 
  int i; 

  for (int i = 0; i < 3; ++i) 
  { 
    std::cout << "Geben Sie eine Zahl ein: " << std::flush; 
    std::cin >> i; 
  } 
} 

Der dritte Schleifentyp, der in C++ verwendet werden kann, ist eine Zählschleife. Sie wird for-Schleife genannt.

Die for-Schleife ist ähnlich aufgebaut wie die while-Schleife: Zuerst wird das Schlüsselwort gesetzt, dann folgt eine runde Klammer, danach folgt der Anweisungsblock der Schleife. Im Gegensatz zur while-Schleife besteht die runde Klammer jedoch in einer for-Schleife nicht mehr nur aus der Überprüfung einer Bedingung, sondern aus drei verschiedenen Bereichen.

Eine for-Schleife unterteilt die runde Klammer in genau drei Bereiche. Zur Abgrenzung der Bereiche werden Semikolons gesetzt. Der erste Bereich - von der geöffneten runden Klammer zum ersten Semikolon - ist der Initialisierungsbereich. Der zweite Bereich - vom ersten Semikolon zum zweiten Semikolon - ist die Schleifenbedingung. Der dritte Bereich - vom zweiten Semikolon zur geschlossenen runden Klammer - ist die Wertänderung.

Die schematische Darstellung der for-Schleife sieht demnach wie folgt aus.

for (INITIALISIERUNG; BEDINGUNG; WERTÄNDERUNG) 
{ 
  ANWEISUNGSBLOCK 
} 

Trifft die Programmausführung auf eine for-Schleife, wird zuallererst der Initialisierungsbereich ausgeführt. Für gewöhnlich wird hier - daher der Name - eine Variable initialisiert, also auf einen bestimmten Wert gesetzt. Sie können hier auf eine bereits bestehende Variable zurückgreifen oder aber auch eine neue Variable definieren.

Ist der Initialisierungsbereich ausgeführt worden, wird die Bedingung überprüft. Diese besteht wie gewohnt meist aus einem Vergleich zweier Werte oder Variablen. Ist die Bedingung wahr - wird also das Ergebnis true zurückgegeben - wird der Anweisungsblock der Schleife ausgeführt. Ist die Bedingung falsch, wird der Anweisungsblock ausgelassen, und die Programmausführung setzt hinter der for-Schleife fort.

Wenn die Bedingung wahr war und der Anweisungsblock einmal ausgeführt wurde, wird anschließend der dritte Bereich in den runden Klammern der for-Schleife ausgeführt - die Wertänderung. Hier wird normalerweise die Variable, die im Initialisierungsbereich auf einen bestimmten Wert gesetzt wurde, in- oder dekrementiert. An dieser Stelle kommt daher häufig der Inkrement- oder Dekrement-Operator zum Einsatz.

Nachdem die Wertänderung vorgenommen wurde, wird wieder die Bedingung im Schleifenkopf überprüft. Ist sie immer noch wahr, wird der Anweisungsblock erneut ausgeführt. Ist sie falsch, wird die Schleife beendet, und die Programmausführung setzt hinter der Schleife fort.

Die for-Schleife wird normalerweise dann eingesetzt, wenn mehr oder weniger klar ist, wie oft der Anweisungsblock ausgeführt werden muss. Bei der while- und do-while-Schleife ist das normalerweise nicht bekannt, so dass in diesem Fall auch keine Variable die Anzahl der Schleifendurchläufe mitzählt, sondern lediglich eine Bedingung überprüft wird. Dennoch lassen sich die Schleifen beliebig austauschen. Den richtigen Schleifentyp für bestimmte Programmiersituationen gibt es nicht.


4.3 Kontrolltransferanweisungen

Abbrechen und Neuausführen

In C++ stehen zwei Schlüsselwörter zur Verfügung, um eine laufende Code-Ausführung abzubrechen oder neu zu starten. Ein Schlüsselwort haben Sie bereits kennengelernt: break.

Mit break können nicht nur switch-case-Kontrollstrukturen beendet werden, sondern auch alle Schleifentypen. Betrachten Sie folgendes Beispiel.

#include <iostream> 

int main() 
{ 
  int i; 

  do 
  { 
    std::cout << "Geben Sie eine Zahl groesser gleich 100 ein: " << std::flush; 
    std::cin >> i; 
    if (!std::cin.good()) 
        break; 
  } 
  while (i < 100); 
} 

Der Anwender wird innerhalb einer while-Schleife aufgefordert, eine Zahl größer oder gleich 100 einzugeben. Die Schleifenbedingung ist erst dann falsch, wenn er dies getan hat. Solange er eine Zahl kleiner als 100 eingibt, wird die Schleife wiederholt ausgeführt.

Nach jeder Eingabe durch den Anwender erfolgt jedoch vor der Überprüfung der Schleifenbedingung innerhalb einer if-Kontrollstruktur ein Test, ob die Standardeingabe std::cin in einem fehlerfreien Zustand ist. Sie ist dies nicht, wenn keine zum Datentyp der Variable i passende Eingabe durch den Anwender vorgenommen wurde. Gibt der Anwender zum Beispiel keine Zahl ein, sondern drückt einfach Enter, speichert std::cin diesen Fehler. Indem für std::cin die Methode good() aufgerufen wird, kann überprüft werden, ob der aktuelle Zustand von std::cin fehlerfrei ist. good() gibt true oder false zurück. Wenn im obigen Beispielprogramm false zurückgegeben wird, ist die Bedindung aufgrund des NICHT-Operators wahr und die Schleife wird mit break abgebrochen.

Das Schlüsselwort break darf ausschließlich innerhalb von switch-case-Anweisungen und Schleifen verwendet werden. Ansonsten meldet der Compiler einen Fehler.

Das zweite Schlüsselwort, das in die Kategorie Kontrolltransferanweisungen fällt, ist continue. Betrachen Sie hierzu folgendes Beispiel.

#include <iostream> 

int main() 
{ 
  int i; 

  while (true) 
  { 
    std::cout << "Geben Sie eine negative Zahl ein: " << std::flush; 
    std::cin >> i; 
    if (i >= 0) 
      continue; 
    break; 
  } 
} 

Das Schlüsselwort continue darf ausschließlich in Schleifen verwendet werden. Im obigen Beispiel steht es im Anweisungsblock einer while-Schleife. Diese Schleife ist insofern bemerkenswert, als dass die Bedingung immer true ergibt - genau das ist nämlich hinter dem Schlüsselwort while in runden Klammern angegeben. Die Schleife würde demnach unendlich oft ausgeführt werden, wenn es keine andere Abbruchbedingung in der Schleife gibt.

Innerhalb des Anweisungsblocks der Schleife wird der Anwender aufgefordert, eine negative Zahl einzugeben. Die Eingabe wird daraufhin in einer if-Kontrollstruktur überprüft, ob es sich tatsächlich um eine negative Zahl handelt. Ist dies nicht der Fall, wird der Anweisungsblock hinter if ausgeführt, der aus dem Schlüsselwort continue besteht. continue bewirkt, dass der aktuelle Schleifendurchgang abgebrochen wird. Die Code-Ausführung setzt also am Schleifenbeginn fort, es wird demnach die Bedingung erneut überprüft. Nachdem diese im Beispiel immer true ergibt, wird die Schleife neugestartet.

Gibt der Anwender hingegen eine negative Zahl ein, wird die Schleife nicht mit continue neugestartet, sondern mit break beendet. Somit ist sichergestellt, dass das Programm nicht in einer Endlosschleife hängenbleibt.

Obiges Beispielprogramm demonstriert lediglich die Funktion von continue. Normalerweise könnte die Funktionalität der Anwendung besser und einfacher implementiert werden, ohne auf continue und break zurückgreifen zu müssen. Derartige Kontrolltransferanweisungen sollten nur eingesetzt werden, wenn eine andere Schreibweise das Programm zu kompliziert gestalten würde. In der Praxis werden continue und break jedenfalls nicht sehr häufig verwendet.


4.4 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Entwickeln Sie eine C++-Anwendung, die den Anwender zur Eingabe einer vierstelligen Zahl auffordert. Das Programm soll daraufhin die Quersumme der Zahl mit Hilfe einer Schleife errechnen und das Ergebnis dann auf den Bildschirm ausgeben.

  2. Erweitern Sie das Programm aus Aufgabe 1 derart, dass die Quersummer einer beliebigen Zahl errechnet wird.

  3. Entwickeln Sie eine C++-Anwendung, die den Anwender zur Eingabe einer Zahl auffordert. Das Programm soll daraufhin errechnen, ob es sich um eine Primzahl handelt. Ist dies der Fall, soll eine entsprechende Meldung auf den Bildschirm ausgegeben werden. Andernfalls soll die Meldung ausgegeben werden, dass es sich um keine Primzahl handelt.

  4. Entwickeln Sie eine C++-Anwendung, die die kleinste und größte Zahl von vier vorgegebenen Zahlen ermittelt, die in einem Array vom Typ int gespeichert sind. Das Programm soll auf den Bildschirm die größte und die kleinste Zahl aus dem Array ausgeben. Testen Sie Ihr Programm, indem Sie im Array verschiedene Zahlen speichern und die größte und kleinste Zahl sich jeweils an verschiedenen Stellen im Array befindet.