C++-Programmierung/ Einführung in C++/ Verzweigungen


Eine Verzweigung (bedingte Anweisung, conditional statement) dient dazu, ein Programm in mehrere Pfade aufzuteilen. Beispielsweise kann so auf Eingaben des Benutzers reagiert werden. Je nachdem, was der Benutzer eingibt, ändert sich der Programmablauf.

Verzweigungen werden mit dem Schlüsselwort if begonnen. In der einfachsten Form sieht das so aus:

Syntax:
if(«Bedingung») «Anweisung»
«Nicht-C++-Code», »optional«

Wenn die Bedingung erfüllt ist, wird die Anweisung ausgeführt, ansonsten wird sie übersprungen. Sollen nicht nur eine, sondern mehrere Anweisungen ausgeführt werden, fassen Sie diese mit {...} zu einer Blockanweisung zusammen:

Syntax:
if(«Bedingung»){
    «Anweisungen»
}
«Nicht-C++-Code», »optional«

Als Bedingung darf jeder Ausdruck verwendet werden, der einen bool zurückgibt oder dessen Ergebnis sich in einen bool umwandeln lässt. Ganzzahlige und Gleitkommadatentypen lassen sich nach bool umwandeln, die Regel lautet: Ist eine Zahl (exakt) gleich 0, so wird sie als false ausgewertet, andernfalls als true.

int i;
cin >> i;
if(i){
    cout << "Der Benutzer hat einen Wert ungleich 0 eingegeben\n";
}

Andernfalls

Bearbeiten

Das Schlüsselwort else erweitert die Einsatzmöglichkeiten der Verzweigung. Während ein normales (also einzelnes) if einen bestimmten Teil des Codes ausführt, falls eine Bedingung erfüllt ist, stellt else eine Erweiterung dar, anderen Code auszuführen, falls die Bedingung nicht erfüllt ist.

int i;
cin >> i;
if (i)
    cout << "Sie haben einen Wert ungleich 0 eingegeben!\n";
else
    cout << "Sie haben 0 eingegeben!\n";

Natürlich könnten auch hier, sowohl für die if-Anweisung, als auch für die else-Anweisung, ein Anweisungsblock stehen. Wenn Sie Pascal oder eine ähnliche Programmiersprache kennen, wird Ihnen auffallen, dass auch die Anweisung vor dem else mit einem Semikolon abgeschlossen wird. Da auf eine if- oder else-Anweisung immer nur eine Anweisung oder ein Anweisungsblock stehen kann, muss zwangsläufig direkt danach ein else stehen, um dem if zugeordnet zu werden.

Sie können in einer Verzweigungsanweisung auch mehr als zwei Alternativen angeben:

int i;
cin >> i; 
if (i == 10)
    cout << "Sie haben zehn eingegeben\n";
else
    if (i == 11)
        cout << "Sie haben elf eingegeben\n";
    else
        cout << "Sie haben weder zehn noch elf eingegeben\n";

Es können beliebig viele Zweige mit else if vorkommen. Allerdings ist es üblich, eine andere Einrückung zu wählen, wenn solche „if-else-Bäume“ ausgebaut werden:

int i;
cin >> i; 
if (i == 10)
    cout << "Sie haben zehn eingegeben\n";
else if (i == 11)
    cout << "Sie haben elf eingegeben\n";
else
    cout << "Sie haben weder zehn noch elf eingegeben\n";

Außerdem ist es zu empfehlen, auch bei einer Anweisung einen Anweisungsblock zu benutzen. Letztlich ist die Funktionalität immer die gleiche, aber solche Blöcke erhöhen die Übersichtlichkeit und wenn Sie später mehrere Anweisungen, statt nur einer angeben möchten, brauchen Sie sich um etwaige Klammern keine Gedanken zu machen, weil sie sowieso schon vorhanden sind.

int i;
cin >> i; 
if (i == 10) {
    cout << "Sie haben zehn eingegeben\n";
} else if(i == 11) {
    cout << "Sie haben elf eingegeben\n";
} else {
    cout << "Sie haben weder zehn noch elf eingegeben\n";
}

Sie werden für die Positionierung der Klammern übrigens auch oft auf eine andere Variante treffen:

int i;
cin >> i; 
if (i == 10)
{
    cout << "Sie haben zehn eingegeben\n";
}
else
{
    if (i == 11)
    {
        cout << "Sie haben elf eingegeben\n";
    }
    else
    {
        cout << "Sie haben weder zehn noch elf eingegeben\n";
    }
}

Einige Programmierer finden dies übersichtlicher, für dieses Buch wurde jedoch die Variante mit den öffnenden Klammern ohne Extrazeile verwendet. Das hat den Vorteil, dass weniger Platz benötigt wird und da die Einrückung ohnehin die Zugehörigkeit andeutet, ist eine zusätzliche Kennzeichnung nicht unbedingt nötig.

Die Einrückung von Quelltextzeilen hat für den Compiler übrigens keine Bedeutung. Sie ist lediglich eine grafische Darstellungshilfe für den Programmierer. Auch die in diesem Buch gewählte Einrückungstiefe von vier Leerzeichen ist optional, viele Programmierer verwenden etwa nur zwei Leerzeichen. Andere hingegen sind davon überzeugt, dass acht die ideale Wahl ist. Aber egal, wofür Sie sich entscheiden, wichtig ist, dass Sie Ihren Stil einhalten und nicht ständig ihren Stil wechseln. Das verwirrt nicht nur, sondern sieht auch nicht schön aus.

Tipp

Wenn es Sie nicht stört, in Ihrem Texteditor Tabulatorzeichen und Leerzeichen anzeigen zu lassen, dann sollten Sie für die Einrückung Tabulatorzeichen verwenden und für alles hinter der normalen Einrückung (etwa den Abstand bis zum Kommentar) Leerzeichen. Das hat den Vorteil, dass Sie die Einrückungstiefe jederzeit ändern können, indem Sie angeben wie viele Leerzeichen einem Tabulatorzeichen entsprechen.
Nachteil: Wenn Sie den Quelltext in verschiedenen Editoren bearbeiten oder weitergeben, muss in jedem Editor eingestellt werden, was die Tabulator-Breite sein soll. Beim Verwenden von Leerzeichen bleibt die Einrückung immer gleich, auch wenn die Tabulatorbreite verschieden eingestellt sein sollte.

Vergleichsoperatoren

Bearbeiten

Im obigen Beispiel kam schon der Vergleichsoperator == zum Einsatz. In C++ gibt es insgesamt sechs Vergleichsoperatoren. Sie liefern jeweils den Wert true, wenn die beiden Operanden (die links und rechts des Operators stehen) dem Vergleichskriterium genügen, ansonsten den Wert false.

== identisch
<= ist kleiner (oder) gleich
>= ist größer (oder) gleich
< ist kleiner
> ist größer
!= ist ungleich
Hinweis

Der Vergleichsoperator == wird von Anfängern oft mit dem Zuweisungsoperator = verwechselt. Da es absolut legal ist, eine Zuweisung innerhalb einer if-Bedingung zu machen, führt das oft zu schwer zu findenden Fehlern. Eine Zuweisung wird ausgewertet zum zugewiesenen Wert. Problem-Beispiel:

int a = 5, b = 8;
if (a = b) cout << "5 ist gleich 8.";
Ausgabe:
5 ist gleich 8.

Die „Bedingung“ weist den Wert von b an die Variable a zu (a = 8). Für das if wird der Gesamtausdruck ausgewertet (also 8), was true bedeutet.

Prüfen Sie bei seltsamen Verhalten also immer, ob vielleicht der Zuweisungsoperator = statt des Gleichheitsoperators == verwendet wurde.

Eine weitere Falle ist der Ungleichheitsoperator !=, wenn er falsch herum geschrieben wird (=!). Letzteres sind in Wahrheit zwei Operatoren, nämlich die Zuweisung = und die logische Negierung !, die Sie gleich kennen lernen werden. Um das zu unterscheiden, machen Sie sich einfach klar, was das in Worten heißt:

  • != – nicht gleich
  • =! – gleich nicht

Logische Operatoren

Bearbeiten

Mit logischen Operatoren können Sie mehrere Bedingungen zu einem Ausdruck verknüpfen. C++ bietet folgende Möglichkeiten:

! Logisches Nicht Resultat wahr, wenn der Operand falsch ist
&& Logisches Und Resultat wahr, wenn beide Operanden wahr sind
|| Logisches Oder Resultat wahr, wenn mindestens ein Operand wahr ist (inclusive-or)

Die Operatoren lassen sich übersichtlich mit Wahrheitstafeln beschreiben (bei der hier gewählten Darstellung ist jede Spalte für sich zu lesen):

Logisches Und (&&)
a true true false false
b true false true false
a && b true false false false

Beispiel für die dritte Spalte: Mit a = false und b = true gilt a && b -> false.

Logisches Oder (||)
a true true false false
b true false true false
a || b true true true false
Logisches Nicht (!)
a true false
!a false true

Beispiel:

int i = 10, j = 20;
if (i == 10 && j == 10) {
    cout << "Beide Werte sind gleich zehn\n";
}
if (i == 10 || j == 10) {
    cout << "Ein Wert oder beide Werte sind gleich zehn\n";
}
Ausgabe:
Ein Wert oder beide Werte sind gleich zehn

Aus Gründen der Lesbarkeit sollten Vergleichsausdrücke grundsätzlich von Klammern umgeben sein. Der obige Code würde folglich so aussehen:

int i = 10, j = 20;
if ((i == 10) && (j == 10)) {
    cout << "Beide Werte sind gleich zehn\n";
}
if ((i == 10) || (j == 10)) {
    cout << "Ein Wert oder beide Werte sind gleich zehn\n";
}
Ausgabe:
Ein Wert oder beide Werte sind gleich zehn

Sowohl beim &&-Operatoren (Logik-und) als auch beim ||-Operator (Logik-oder) werden die Teilausdrücke von links nach rechts bewertet, und zwar nur so lange, bis das Resultat feststeht. Wenn z. B. bei einer &&-Verknüpfung A && B && C schon die erste Bedingung 'A' falsch ist, werden 'B' und 'C' gar nicht mehr untersucht, da bei && ja alle Bedingungen true sein müssen. Der Rückgabewert der beiden Operatoren ist vom Typ bool.

Gelegentlich ist daher anzutreffen:

if ( (variable_ist_gueltig) && (variable_erfuellt_zusaetzliche_detailbedingung) ) { /* mache etwas */ }

Ob die Variable die Detailbedingung erfüllt, kann nur geprüft werden, wenn sie einen gültigen Wert enthält. Die erste Bedingung auf Gültigkeit schützt somit die nachfolgende davor, mit ungültigen Werten arbeiten zu müssen.

Hinweis

Beachten Sie bitte, dass der UND-Operator (&&) eine höhere Priorität als der ODER-Operator (||) hat. Das heißt, Sie müssen bei Ausdrücken wie dem Folgenden vorsichtig sein.

int i = 10, j = 20;
// Erwartete Reihenfolge    ((((i == 10) || (j == 20)) && (j == 20)) && (i == 5))
// Tatsächliche Reihenfolge ((i == 10) || (((j == 20) && (j == 20)) && (i == 5)))
if (i == 10 || j == 20 && j == 20 && i == 5) {
    cout << "i ist Zehn und fünf oder (j ist Zwanzig und i fünf)!\n";
} else {
    cout << "i ist nicht Zehn oder (j ist Zwanzig oder i nicht fünf)!\n";
}
Ausgabe:
i ist Zehn und fünf oder (j ist Zwanzig und i fünf)!

Die Ausgabe ist von der Logik her falsch, weil der Ausdruck in der Reihenfolge ((i == 10) || (((j == 20) && (j == 20)) && (i == 5))) ausgewertet wird. Solche Fehler sind sehr schwer zu finden, also sollten Sie sie auch nicht machen. Daher der Tipp: Verwenden Sie bei solch komplexen Bedingungen immer Klammern, um klar zu machen, in welcher Reihenfolge Sie die Ausdrücke auswerten wollen. Das ist unmissverständlich, und der menschliche Leser liest den Ausdruck genauso wie der Compiler. Um zu erkennen, an welcher Stelle eine Klammer wieder geschlossen wird, beherrschen die meisten Editoren das sogenannte Bracket Matching. Dabei hebt der Editor (automatisch oder über einen bestimmten Hotkey) die schließende Klammer hervor.

Tipp

Wenn Sie mit mehreren && und || arbeiten, dann schreiben Sie den Ausdruck, der am wahrscheinlichsten zutrifft, auch am weitesten links, also vor den anderen Ausdrücken. Das ist eine (zugegebenermaßen) sehr geringfügige Optimierung, aber es gibt Situationen in denen sie trotzdem sinnvoll ist. Beispielsweise wenn die Bedingung innerhalb einer Schleife sehr oft ausgeführt wird.

Hinweis für fortgeschrittene Leser: Beachten Sie bitte, dass für überladende Operatoren andere Regeln gelten. Alle diejenigen, die noch nicht wissen, was überladende Operatoren sind, brauchen sich um diesen Hinweis (noch) nicht zu kümmern.

Bedingter Ausdruck

Bearbeiten

Häufig werden Verzweigungen eingesetzt, um abhängig vom Wert eines Ausdrucks eine Zuweisung vorzunehmen. Das können Sie mit dem Auswahloperator ? ... : ... auch einfacher formulieren:

min = a < b ? a : b;
// Alternativ ginge:
if (a < b) {
    min = a;
} else {
    min = b;
}

Grafisch sieht das so aus:

 

Der Variablen min wird der kleinere, der beiden Werte a und b zugewiesen. Analog zum Verhalten der logischen Operatoren wird nur derjenige „Zweig“ bewertet, der nach Auswertung der Bedingung (a < b) tatsächlich ausgeführt wird.


Der Bedingungsoperator kann auch wie folgt verwendet werden:

(zahl%2) ? printf("ungerade") : printf("gerade");

Im Gegensatz zur if...else-Anweisung ist es hier aber nicht möglich, in Abhängigkeit zu einer Bedingung, nur eine Anweisung auszugeben, indem man die else-Anweisung einfach weg lässt:

if(a) make(b); 	//richtig

(a) ? make(b);	//falsch

Das liegt daran, dass der Bedingungsoperator ?: keine Kontrollstruktur im eigentlichen Sinne ist.

Vielmehr wird mit (a) ? make(b) : make(c); ein Ausdruck „berechnet“, der einen Wert aufweisen muss, während sich die if-Anweisung logisch in einen "Tu-nichts-Pfad" auflösen kann.