Zusammenfassung
Ein Programm, das nur Anweisungen aneinanderreiht, tut immer dasselbe, welche Daten es auch erhält; damit es auf sie reagiert, muss es entscheiden. Diese Lektion behandelt die Entscheidungen von C++: if und else, die je nach einer Bedingung zwischen zwei Wegen wählen; die Ketten von else if, die zwischen mehreren wählen und in denen die Reihenfolge der Bedingungen festlegt, welchen Fall jeder Zweig behandelt; die verschachtelten Entscheidungen und die Falle des hängenden else; und switch mit enum class, die zwischen benannten diskreten Werten wählen, mit einem Compiler, der warnt, wenn ein Fall fehlt oder wenn einer in den nächsten durchfällt. Der Begriff, der alles vereint, ist die Fallunterscheidung: Eine korrekte Entscheidung zerlegt die Menge der Eingaben in disjunkte Fälle, die sie vollständig überdecken, und jeder Fall wird mit einem Test in seinem Inneren und einem weiteren an jeder seiner Grenzen geprüft.
Lernziele
- Einfache, verkettete und verschachtelte Entscheidungen schreiben, die alle Fälle einer Spezifikation behandeln.
- Prüfen, dass die Fälle einer Kette aus
ifundelse ifdisjunkt und vollständig sind, und eine Testeingabe pro Fall entwerfen. switchmitenum classfür diskrete Werte verwenden und erklären, wovor der Compiler warnt, wenn ein Fall fehlt.- Das hängende
elseund das Durchfallen zwischen den Fällen einesswitcherkennen und beheben.
if und else
Die Form einer Entscheidung
Die Lektion Vom Problem zum Algorithmus hat drei Formen vorgestellt, aus denen jeder Algorithmus aufgebaut wird: die Sequenz, die Entscheidung und die Wiederholung. Bisher waren die Programme des Kurses Sequenzen, abgesehen von einem vorgezogenen if in Einheit 1; diese Lektion schreibt die zweite Form in C++. Eine Entscheidung wertet eine Bedingung aus, also einen Ausdruck vom Typ bool wie die der Lektion Relationen und Logik, und führt einen Block aus, wenn die Bedingung wahr ist, und einen anderen, wenn sie falsch ist:
if (Bedingung) { // wird ausgeführt, wenn die Bedingung wahr ist} else { // wird ausgeführt, wenn die Bedingung falsch ist}Die Bedingung steht immer in Klammern. Der else-Teil ist optional: Ohne ihn geschieht nichts, wenn die Bedingung falsch ist, und das Programm fährt mit der Anweisung nach der Entscheidung fort. In beiden Fällen treffen sich die zwei Wege am Ende, sodass die Entscheidung, von außen gesehen, eine einzige Anweisung mit einem Eingang und einem Ausgang ist, wie es Lektion 1.1 verlangte.
Die Lektion Arithmetik hat gezeigt, dass a % b das Vorzeichen des Dividenden hat, sodass der Ausdruck mit und den Wert hat, während der euklidische Rest, der nie negativ ist, 1 beträgt; und sie hat angekündigt, dass der sichere Weg, ihn zu korrigieren, darin besteht, nur dann zu addieren, wenn der Rest negativ ist. Das Programm setzt voraus.
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; int r{a % b}; if (r < 0) { r = r + b; } std::println("Euklidischer Rest von {} durch {}: {}", a, b, r);}-7 2Euklidischer Rest von -7 durch 2: 1Die Variable r ist nicht mehr konstant, weil die Entscheidung sie ändern kann. Die Abbildung verfolgt die beiden möglichen Wege.
Beweis
Die Ausführung erreicht die Entscheidung mit bereits berechnetem
r, und als Erstes wertet sie die Bedingungr < 0aus: Die Raute ist der einzige Punkt des Programms, an dem sich die Wege trennen.Ist die Bedingung wahr, wie bei und 2, wird der Block des
ifausgeführt:rgeht von auf 1 über.Ist sie falsch, wie bei 7 und 2, wird der Block vollständig übersprungen, und
rbehält seinen Wert.Beide Wege treffen sich am selben Punkt, und von dort an läuft das Programm in beiden Fällen gleich weiter: Die Anweisung, die das Ergebnis ausgibt, wird immer ausgeführt.
Mit else wählt die Entscheidung zwischen zwei Handlungen, die einander ausschließen. Die Schaltjahrbedingung der Lektion Relationen und Logik liefert einen bool; eine Entscheidung macht daraus ein Verhalten des Programms.
#include <iostream>#include <print>int main(){ int year{}; std::cin >> year; const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}; if (isLeap) { std::println("{} ist ein Schaltjahr: Der Februar hat 29 Tage.", year); } else { std::println("{} ist kein Schaltjahr: Der Februar hat 28 Tage.", year); }}19001900 ist kein Schaltjahr: Der Februar hat 28 Tage.Der Bedingung einen Namen zu geben, wie isLeap, trennt das, was entschieden wird, von dem, was mit der Entscheidung geschieht.
Immer geschweifte Klammern
Die Grammatik von C++ erlaubt es, die geschweiften Klammern wegzulassen, wenn der Block nur eine Anweisung enthält: if (r < 0) r = r + b; ist gültig. Nun steuert die Entscheidung ohne Klammern nur die nächste Anweisung, und was der Leser für gesteuert hält, hängt von der Einrückung ab, die der Compiler ignoriert. Wenn dem so ist, kann jede spätere Änderung (eine zweite Anweisung hinzufügen, die erste auskommentieren) die Bedeutung ändern, ohne dass der Text es zeigt. Daraus folgt die Regel des Kurses: Der Block eines if oder eines else erhält immer geschweifte Klammern, auch wenn er nur eine Anweisung enthält.
Der kürzeste Fehler von allen bestätigt die Regel. Ein Semikolon nach den runden Klammern ist eine leere Anweisung, und die Entscheidung steuert diese leere Anweisung; der folgende Block gehört nicht mehr zum if und wird immer ausgeführt.
#include <print>int main(){ const int stock{12}; if (stock == 0); { std::println("Produkt ausverkauft"); } std::println("Verfügbare Einheiten: {}", stock);}main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body] 6 | if (stock == 0); | ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation] 6 | if (stock == 0); | ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’ 7 | { | ^Produkt ausverkauftVerfügbare Einheiten: 12Das Programm meldet, das Produkt sei ausverkauft, obwohl noch 12 Einheiten vorhanden sind. GCC gibt zwei Warnungen aus: -Wempty-body schlägt geschweifte Klammern um den leeren Rumpf vor, und -Wmisleading-indentation weist darauf hin, dass der Block so eingerückt ist, als würde das if ihn steuern. Der andere klassische Fehler, if (x = 0) statt if (x == 0), wurde in der Lektion Kompilieren, Ausführen und Konfigurieren gezeigt: Die Zuweisung hat den Wert null, der in false umgewandelt wird, und -Wparentheses warnt. In beiden Fällen kompiliert das Programm; ohne die Warnungen bliebe der Fehler unbemerkt.
Die geschweiften Klammern haben noch eine Folge: Eine im Block eines if deklarierte Variable existiert nur dort. Wird ein in einem Zweig berechneter Wert nach der Entscheidung verwendet, so wird die Variable vorher deklariert, wie r im Beispiel des Rests; der Gültigkeitsbereich wird in der Lektion Parameter und Gültigkeitsbereich behandelt.
Der Bedingungsoperator
Wenn die beiden Zweige einer Entscheidung nur einen Wert wählen, bietet C++ eine kürzere Form: Bedingung ? a : b hat den Wert a, wenn die Bedingung wahr ist, und b, wenn sie falsch ist, und wertet nur das gewählte Element aus. Anders als if, das eine Anweisung ist, ist der Bedingungsoperator ein Ausdruck: Er hat einen Wert und einen Typ und kann überall stehen, wo ein Ausdruck stehen kann, etwa in der Initialisierung einer Konstanten, die ein if nicht erlauben würde, ohne auf const zu verzichten.
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; const int larger{a > b ? a : b}; std::println("Größerer Wert: {}", larger); std::println("{} {}", b, b == 1 ? "Einheit" : "Einheiten"); const auto mixed{a > b ? a : 0.5}; std::println("Gemeinsamer Typ: {:.1f}", mixed); std::println("Ohne Klammern: {}", 10 + a > b ? 1 : 0); std::println("Mit Klammern: {}", 10 + (a > b ? 1 : 0));}7 3Größerer Wert: 73 EinheitenGemeinsamer Typ: 7.0Ohne Klammern: 1Mit Klammern: 11Die ersten beiden Zeilen zeigen den Gebrauch, der Klarheit schafft: den größeren Wert wählen und ein Wort an die Zahl angleichen, ohne die ganze Anweisung in zwei Zweigen zu wiederholen. Die dritte zeigt, dass der Ausdruck einen einzigen Typ hat, welcher Zweig auch gewählt wird: Haben die beiden Ergebnisse verschiedene Typen, so werden sie mit den üblichen arithmetischen Konvertierungen der Lektion Arithmetik in einen gemeinsamen Typ umgewandelt; hier ergeben ein int und ein double einen double, und die gewählte 7 wird als 7.0 gespeichert. Die letzten beiden zeigen den Vorrang: Der Bedingungsoperator gehört zu den Operatoren mit dem niedrigsten Vorrang, sodass ohne Klammern die Bedingung 10 + a > b lautet und der Wert 1 statt 11 ist. Ist der Bedingungsoperator Teil eines größeren Ausdrucks, so steht er in Klammern.
Der Operator verdunkelt, sobald man mehr von ihm verlangt als die Wahl eines Werts. Verschachtelt, wie in a > b ? (a > c ? a : c) : (b > c ? b : c), zwingt er dazu, eine verschachtelte Entscheidung im Kopf zu rekonstruieren, ohne die geschweiften Klammern, die sie lesbar machen; und seiner Wirkungen wegen eingesetzt, wie in ok ? std::println("ja") : std::println("nein"), ist er ein verkleidetes if. Die Regel des Kurses lautet, ihn nur zur Wahl zwischen zwei kurzen Werten zu verwenden, ohne ihn zu verschachteln; alles andere wird mit if geschrieben.
Inhalt wird geladen…
Abgeschlossen
Melde dich an, um deinen Fortschritt zu speichern