C++: Die Basics - eps1.13_Programmablauf
Hallo und herzlich willkommen. Wie ist Ihre Bestellung?
„Guten Tag, ich hätte gerne den Burger des Monats im Menü mit Curly Fries.“
Das tut uns sehr leid, doch leider sind Curly Fries im Moment aus. Dürfen es auch Pommes sein?
„Schade, aber na gut. Damit bin ich alternativ einverstanden.“
Welches Getränk möchten Sie gerne zu Ihrem Menü?
…
Den weiteren Verlauf des Gesprächs kannst du dir bestimmt denken.
Dialoge in dieser Art sind ganz alltäglich. Von einem Ausgangspunkt ergeben sich mehrere Wege zu unterschiedlichen oder manchmal auch vergleichbaren Richtungen. Doch der genaue Ablauf ist in den meisten Situationen nicht vorhersehbar und von einigen Faktoren abhängig.
Das gilt auch für das Verhalten von Systemen und deiner Software. Häufig hängt die nächste Anweisung von einer Bedingung ab. Doch wie reagiert dein Programm auf Entscheidungen und wie steuerst du den weiteren Ablauf?
Bisher haben unsere Programme alle Anweisungen von oben nach unten seriell abgearbeitet. Doch das entspricht meist nicht den Anforderungen der realen Welt. Dort müssen die äußeren Umstände festgestellt und flexibel darauf gehandhabt werden.
Die Informationstechnik kennt für die Steuerung des Programmablaufs die beiden Konzepte der Bedingten Anweisungen und der Schleifen.
Auswahl und Verzweigung
Als erstes schauen wir uns das if ... else
Konstrukt an. Mit diesem lässt sich unterschiedlich auf Bedingungen reagieren. Du kannst das Konstrukt sehr wörtlich nehmen. Wenn if
die Bedingung eintritt, dann führe folgende Anweisungen aus. Andernfalls else
nimm die anderen Anweisungen.
Der Ablauf verzweigt sich also. Abhängig davon, ob die Bedingung true
oder false
ergibt, wird einer der beiden Äste weiter verfolgt.
Wie sieht das if ... else
Konstrukt im Quellcode aus? Eingeleitet vom Schlüsselwort if folgt in runden Klammern ()
ein Ausdruck, der die Bedingung formuliert. Der Ausdruck wird als bool ausgewertet. Das Besondere dabei: 0
wird als false
interpretiert und jeder abweichende Wert von 0
als true
.
Anschließend kommen in geschweiften Klammern {}
die Anweisungen, die ausgeführt werden sollen, wenn die Bedingung erfüllt ist. Gehören Anweisungen zusammen und werden von den Klammern umschlossen, wird häufig auch von Verbundanweisungen oder compound statements gesprochen.
Damit hast du eine vollwertige und standardkonforme if Anweisung. Du kannst sie so stehen lassen. Wird die Bedingung erfüllt, dann geht das Programm in die Klammern weiter. Andernfalls überspringt das Prorgamm den Klammerinhalt und setzt die Ausführung danach fort.
Optional kannst du auch vorgeben, was geschehen soll, wenn die Bedingung nicht erfüllt, also false
wird. Dazu musst du direkt im Anschluss zu der if
Anweisung eine else
Anweisung schreiben. Hier benötigst du keine explizit formulierter Bedingung. Der else
Zweig ist implizit die Negation der if
Bedingung. Du musst nur in geschweiften Klammern deine Anweisungen auflisten.
In listing1 findest du ein Beispiel. Mithilfe der rand()
Funktion aus <stdlib>
wird eine Zufallszahl generiert und anschließend der Rest betrachtet, der bei der Modulo Operation mit 2
herauskommt. Sprich, die boolsche Variable condition
hat zufällig den Wert 0
oder 1
.
Als Bedingung der if Anweisung wird der Wert der Variablen genutzt. Ist condition = true
wird der String condition is true
ausgegeben. Andernfalls else
soll die Konsole den String condition is false
anzeigen.
// listing1: conditional execution with if ... else
#include <iostream>
#include <stdlib.h>
int main()
{
bool condition = rand() % 2;
std::cout << "condition is " << condition << std::endl;
if(condition)
{
std::cout << "condition is true" << std::endl;
}
else
{
std::cout << "condition is false" << std::endl;
}
return 0;
}
Du siehst, nicht in jedem Programmdurchlauf werden alle Teile ausgeführt. In keiner Situation können beide Pfade durchlaufen werden.
Flussdiagramm
Damit die Abfolge in deinem Programm besser ersichtlich ist, wurde das Flussdiagramm entwickelt. Es hilft dir, Prozesse grafisch darzustellen und beschreibt die Reihenfolge von Operationen zur Lösung deiner Problemstellung.
Die Symbolik des Flussdiagramms besteht aus einfachen geometrischen Figuren. Die wichtigsten sind:
- Rechteck mit runden Ecken: Terminator
- Rechteck: Operation
- Raute: Verzweigung
- Parallelogramm: Ein- und Ausgabe
- Linie, Pfeil: Verbindung zum nächsten Element
Jeder Prozess beginnt und endet mit einem Terminator. Rechtecke stehen für Operationen, die ausgeführt werden sollen. Mit einer Raute werden Verzweigungen angezeigt. Darin steht die Bedingung, die erfüllt werden soll. Dort entspringen mehrere alternative Pfade, die das Programm weiter durchlaufen kann. Parallelogramme sind für Ein- und Ausgabe. Verbunden werden alle Elemente entweder mit einer Linie oder einem Pfeil.
Natürlich gibt es auch eine Norm zu diesen Diagrammtypen, die DIN 66001.
Abbildung1 zeigt das Flussdiagramm für listing1.
Heute finden Flussdiagramme seltener Einsatz. Programmiere nutzen lieber Pseudocode, da er ihnen einen ähnlichen Abstraktionsgrad bietet, dabei aber schneller zu erstellen und viel leichter zu verändern ist.
Das gilt meiner Meinung nach nur für Leute, die mit Code umzugehen wissen. Möchtest du mit jemandem, der nur wenig Programmiererfahrung hat, über den Ablauf sprechen, dann sind Flussdiagramme eine gute Wahl.
Nested if
Es gibt Situationen, in denen mehrere Bedingungen erfüllt werden müssen. Daraus ergeben sich verschiedene Konstellationen aus wahr und falsch, auf die unter Umständen unterschiedlich reagiert werden soll.
In diesem Fall kannst du if Abfragen ineinander verschachteln.
// listing2: nested if
#include <iostream>
#include <stdlib.h>
int main()
{
bool condition1 = rand() % 2;
std::cout << "condition is " << condition1 << std::endl;
bool condition2 = rand() % 2;
std::cout << "condition is " << condition2 << std::endl;
if(condition1)
{
if(condition2)
{
std::cout << "condition1 and condition2 is true" << std::endl;
}
else
{
std::cout << "only condition1 is true" << std::endl;
}
}
else
{
if(condition2)
{
std::cout << "only condition2 is true" << std::endl;
}
else
{
std::cout << "no condition is true" << std::endl;
}
}
return 0;
}
Ich gebe zu, listing2 ist kein sehr kreatives Beispiel. Aber ich denke, du kannst dort gut erkennen, wie verschachtelte if Konstrukte funktionieren.
Es ist nicht notwendig, die inneren Anweisungen einzurücken. Dem Compiler ist es egal. Doch sie erhöhen die Lesbarkeit deines Code immens. So erkennst du viel leichter, wo die übergeordneten Klammern anfangen und enden.
Switch case
Nehmen wir einmal an, wir wollen in Abhängigkeit zu dem Wert einer Variable unterschiedlich reagieren. Wir haben also eine Variable, die auf mehrere Bedingungen geprüft werden soll.
Jetzt kannst du natürlich alle Bedingungen mit einem gruppierten if else Konstrukt abfragen. Das ist aber sehr umständlich und verschlechtert die Lesbarkeit deines Quelltexts.
In listing3 wird mit der Variable dice
das Verhalten eines zufälligen Würfelwurfs imitiert. Anhand der Augenzahl wird entschieden, welche Ausgabe auf die Konsole erfolgt.
// listing3: grouped if else
#include <iostream>
#include <stdlib.h>
int main()
{
int dice = rand() % 6 + 1;
if(dice == 1)
{
std::cout << "the dice counts one" << std::endl;
}
else if(dice == 2)
{
std::cout << "the dice counts two" << std::endl;
}
else if(dice == 3)
{
std::cout << "the dice counts three" << std::endl;
}
else if(dice == 4)
{
std::cout << "the dice counts four" << std::endl;
}
else if(dice == 5)
{
std::cout << "the dice counts five" << std::endl;
}
else
{
std::cout << "the dice counts six" << std::endl;
}
return 0;
}
Doch C++ kennt einen besseren Weg für diese Aufgabe: das switch case
Konstrukt.
Die unterschiedlichen Ausgaben hängen hier von einem Integer ab. Mit dem Schlüsselwort switch
wird der Wert einer Integer Variable abgefragt. Die verschiedenen Anweisungsmöglichkeiten werden mit dem Schlüsselwort case
eingeleitet und durchnummeriert.
Der Wert der Integer-Variablen gibt den case
an, der ausgeführt werden soll. Der Compiler springt dann an diese Stelle im Code.
Du musst einen case
nicht explizit beenden. Allerdings werden alle folgenden cases
ebenfalls nacheinander ausgeführt. Mit dem Schlüsselwort break
kannst du das verhindert. Break
veranlasst den Compiler aus dem switch
Konstrukt heraus, hinter die abschließende Klammer zu springen. Ab dort wird der Programmablauf fortgeführt.
Mit einem Beispiel wird es dir sicherlich klarer. Wir nehmen wieder den Würfel aus listing3. Diesmal aber mit switch case
.
Switch
wertet die Zufallszahl dice
aus. Anschließend wird zum entsprechenden case
gesprungen und die Augenzahl des Würfels auf der Konsole ausgegeben. Mit break
wird jeder case
und somit das switch
Konstrukt abgeschlossen.
// listing4: switch case
#include <iostream>
#include <stdlib.h>
int main()
{
enum numbers // enumeration for naming the switch cases
{
one,
two,
three,
four,
five,
six
};
int dice = rand() % 6 + 1; // dice throw, random number
switch(dice)
{
case one:
std::cout << "the dice counts one" << std::endl;
break;
case two:
std::cout << "the dice counts two" << std::endl;
break;
case three:
std::cout << "the dice counts three" << std::endl;
break;
case four:
std::cout << "the dice counts four" << std::endl;
break;
case five:
std::cout << "the dice counts five" << std::endl;
break;
case six:
std::cout << "the dice counts six" << std::endl;
break;
default:
std::cout << "Tell me the default state of a dice" << std::endl;
}
return 0;
}
Da switch()
nur Integer auswerten kann, bieten sich Enumeration an, um den cases aussagekräftige Namen zu geben. Das erhöht die Lesbarkeit deines Codes ungemein.
Bedingungen mit Conditional Operator (?:)
C++ kennt noch einen weiteren und sehr mächtigen Operator zur Auswertung von Bedingungen. Mit dem Conditional Operator ? :
kannst du in einer kompakten Form ein if else Konstrukt ersetzen.
Der Conditional Operator besteht aus zwei Teilen. Zuerst formulierst du deine Bedingung. Damit du den Überblick behältst, setzt du diese besser in Klammern. Darauf folgt der erste Teil des Operators ?
und zeigt das Ende der Bedingung an.
Danach kommt das Ergebnis, dass verwendet werden soll, wenn die Bedingung erfüllt, also true
ist. Der zweite Teil des Operators :
trennt true
von false
. Ihm folgt das Ergebnis bei negativer Auswertung false
der Bedingung.
In listing5 kannst du sehen, wie ein conditional Operator im Quellcode aussieht und ob ich diesen treffend beschreiben konnte.
// listing5: conditional operator
#include <iostream>
int main()
{
int val1 {0};
int val2 {0};
std::cout << "Please, enter a number: " << std::endl;
std::cin >> val1;
std::cout << "Please, enter a second number: " << std::endl;
std::cin >> val2;
int greatVal = (val1 > val2)? val1 : val2; // conditional operator
std::cout << greatVal << " is the greater of both numbers" << std::endl;
return 0;
}
Nutze den conditional operator eher bei simplen Bedingungen. Wird es komplizierter, entscheide dich lieber für if und else.
Wie vieles ist auch die Verwendung dieses Operators eine Geschmacksfrage. Einige Programmierer schätzen seine schlanke Form. Andere wiederum mögen ihn nicht.
Das bleibt hängen
Wir sind jetzt in der Lage, Bedingungen zu formulieren, und können den Programmablauf mit Hilfe Bedingter Anweisungen darauf unterschiedlich reagieren lassen. Mit dem Schlüsselwort if kannst du deine Bedingungen auswerten und die Erfüllung mit einer Anweisung verbinden.
Für den Fall, dass die Bedingung nicht erfüllt wurde, kannst du mit else einen alternativen Programmablauf mit anderen Anweisungen erstellen. Also ergibt die Auswertung deiner Bedingung true
, folgt das Programm dem if-Pfad. Ansonsten folgt es dem else-Pfad.
if else Konstrukte lassen sich auch ineinander verschachteln. Hast du allerdings eine Bedingung, deren Auswertung mehr als nur true
oder false
ergeben kann, dann ist die Verschachtelung nicht nur dein einziges Werkzeug.
C++ bietet dir mit switch case
Konstrukten die Möglichkeit, einfacher auf verschiedene Werte der Bedingung zu reagieren.
Mit dem Conditional Operator ? :
kennen wir nun sogar eine sehr kurze Schreibweise für simple Bedingungen.
Die vorgestellten Konstrukten, Schlüsselwörtern und Operatoren lassen dich deinen Programmablauf sehr gut gestalten. Damit bist du in der Lage, unterschiedlich auf Abfragen und ausgewertete Bedingungen zu reagieren.
Ein anderes Konzept, mit dem du den Ablauf deines Programms steuern kannst, sind Schleifen. Doch davon mehr beim nächsten Mal.
Ich wünsche dir maximalen Erfolg!
Quellen
- [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
- [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
- [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017