Elementare Syntax von C++: Bedingung, Alternative und Mehrfachalternative

Bedingung, Alternative und Mehrfachalternative sind die Kontrollstrukturen, die man einsetzen kann, um den Ablauf eines Programmes zu steuern. ErklÀrt wird ihre Syntax in C++ und es werden einige Spitzfindigkeiten diskutiert, die man bei ihrer Verwendung kennen sollte.
Noch keine Stimmen abgegeben
Noch keine Kommentare

Einordnung des Artikels

Übersicht

Bei der Diskussion der Strukturelemente von Algorithmen und der Formulierung eines Algorithmus in Pseudocode (in Pseudocode und formale Sprachen) wurde bereits auf die zentrale Rolle von Bedingungs-PrĂŒfung, Alternative und Mehrfachalternative hingewiesen. Diese drei Strukturelemente werden natĂŒrlich auch in der Sprache C++ unterstĂŒtzt. Die zugehörige Syntax wird in den folgenden Abschnitten vorgestellt und es werden Beispiele und Aufgaben formuliert, um mit ihrer Anwendung vertraut zu werden.

Die Bedingungs-PrĂŒfung

Im Pseudocode wurde die Bedingungs-PrĂŒfung nicht eigens behandelt, da man sie als einen Spezialfall der Alternative verstehen kann. Da selbst die einfachste Form der Bedingungs-PrĂŒfung zu MissverstĂ€ndnissen fĂŒhren kann, wird sie hier natĂŒrlich besprochen.

Im folgenden Beispiel wird geprĂŒft, ob eine eingegebene Zahl n negativ ist. Falls ja, wird ihr Vorzeichen umgedreht. Anschließend wird die Zahl ausgegeben — sie muss jetzt positiv sein.

int n;
cout << "Bitte eine Zahl eingeben ";
cin >> n;
if (n < 0)
    n = - n;
cout << "n = " << n << endl;            // jetzt muss n >= 0 sein

Allgemein sieht die Bedingungs-PrĂŒfung folgendermaßen aus:

if (Bedingung)
    Anweisung1;
                // Hier ist die Bedingungs-PrĂŒfung zu Ende
                // die folgende Anweisung wird immer ausgefĂŒhrt
Anweisung2;

Zuerst wird die Bedingung geprĂŒft. Falls sie erfĂŒllt ist, wird die Anweisung1 ausgefĂŒhrt; falls nicht, wird Anweisung1 nicht ausgefĂŒhrt. Die darauf folgende Anweisung2 wird immer ausgefĂŒhrt (also unabhĂ€ngig davon, ob die Bedingung erfĂŒllt war oder nicht).

Oben wurde gesagt, dass selbst die Bedingungs-PrĂŒfung zu MissverstĂ€ndnissen fĂŒhren kann, nĂ€mlich dann, wenn man durch eine EinrĂŒckung des Quelltextes einen bestimmten Ablauf suggerieren möchte. Man muss sich aber klarmachen, dass das Ende der Bedingungs-PrĂŒfung nicht durch die EinrĂŒckung bestimmt wird.

Im folgenden Code-Schnipsel werden nach einer Bedingungs-PrĂŒfung zwei Anweisungen ausgefĂŒhrt:

if (Bedingung)
    Anweisung1;
    Anweisung2;

Welche Anweisung wird unter welcher Bedingung ausgefĂŒhrt? Die EinrĂŒckung suggeriert hier, dass beide Anweisungen ausgefĂŒhrt werden, wenn die Bedingung erfĂŒllt ist. Diese Interpretation ist aber falsch. Nach Anweisung1 ist die Bedingungs-PrĂŒfung zu Ende und Anweisung2 wird immer ausgefĂŒhrt.

Soll auch Anweisung2 nur unter der Bedingung ausgefĂŒhrt werden, mĂŒssen Klammern gesetzt werden:

if (Bedingung)
    {
    Anweisung1;
    Anweisung2;
    }
Anweisung3

Jetzt sind die beiden Anweisungen zu einem Block zusammengefasst und der Block wird als Ganzes ausgefĂŒhrt, wenn die Bedingung erfĂŒllt ist. Erst Anweisung3 wird immer ausgefĂŒhrt.

Um zum obigen Beispiel zurĂŒckzukehren:

Falls n negativ ist, soll eine entsprechende Ausgabe erfolgen (Zeile 6) und das Vorzeichen soll umgedreht werden (Zeile 7). Der Quelltext lautet dann (geschweifte Klammern beachten):

int n;
cout << "Bitte eine Zahl eingeben ";
cin >> n;
if (n < 0)
    {
    cout << "es wurde eine negative Zahl eingegeben" << endl;
    n = - n;
    }
cout << "n = " << n << endl;            // jetzt muss n >= 0 sein

Fehlerquelle: Der Compiler beachtet EinrĂŒckungen nicht (er liest sie wie ein Trennungszeichen). Achten Sie darauf, dass Sie den gewĂŒnschten Programm-Ablauf durch Klammern erzwingen mĂŒssen (und nicht durch EinrĂŒckungen).

Die Alternative

Die Syntax der Alternative

Im Pseudocode wurde die Alternative mit den Textbausteinen Falls — dann — sonst formuliert:

Falls (Bedingung)
    dann (Anweisung1);
    sonst (Anweisung2);

In C++ entfÀllt das dann (in manchen Programmiersprachen wird hier then eingesetzt):

if (Bedingung)
    Anweisung1;
else
    Anweisung2;

Möchte man — falls die Bedingung erfĂŒllt ist — anstelle der Anweisung1 mehrere Anweisungen ausfĂŒhren, muss man wieder Klammern setzen (Zeile 2 und 5):

if (Bedingung)
    {
    Anweisung1;
    Anweisung2;
    }
else
    Anweisung3;

Dagegen fĂŒhrt der folgende Gebrauch von if-else zu einem Compiler-Fehler:

if (Bedingung)
    Anweisung1;
    Anweisung2;
else                        // Compiler-Fehler
    Anweisung3;

Der Fehler entsteht dadurch, dass if in der ersten Zeile hier vom Compiler als Bedingungs-PrĂŒfung interpretiert wird, da nach Anweisung1 kein else folgt. Zu dem else in Zeile 4 gibt es kein zugehöriges if.

Verschachtelung von Alternativen

Mehrere Alternativen können ineinander verschachtelt werden, wobei man als Programmierer aber darauf achten sollte, dass die Quelltexte nachvollziehbar bleiben — achten Sie vor allem darauf, die EinrĂŒckungen richtig zu setzen.

Im folgenden Beispiel wird wieder eine Zahl n eingegeben und es werden die drei FĂ€lle untersucht:

  • x < 0
  • 0 ≀ n ≀ 5
  • n > 5.
int n;
cout << "Bitte eine Zahl eingeben ";
cin >>  n;

if (n < 0)
    cout << "x ist negativ" << endl;
else
    if (n > 5)
        cout << "x > 5" << endl;
    else
        cout << "0 <= n <= 5" << endl;

Es ist nicht die einzige Anforderung an den Quelltext, leicht nachvollziehbar zu sein. Gerade wenn sich derartige Abfragen in Schleifen befinden, die sehr oft durchlaufen werden, beanspruchen sie sehr viel Rechenzeit. Daher soll man den wahrscheinlichsten Fall wenn möglich zuerst abfragen. Ist im Beispiel oben etwa n < 0, so wird die zweite Alternative nicht mehr betreten.

Andererseits sollen SonderfĂ€lle immer zuerst abgefragt werden, da es oft vorkommt, dass die Berechnungen, die im Normalfall durchgefĂŒhrt werden, sonst nicht durchgefĂŒhrt werden können (wie eine Division durch null). Meist widersprechen sich die beiden zuletzt genannten Anforderungen.

Das dangling else Problem

Die Verschachtelung von Bedingung oder Alternativen fĂŒhrt oft zur Verwirrung, die das folgende Beispiel demonstriert. Es werden zwei int-Variablen x und y deklariert und eingegeben. Anschließend wird in verschachtelten if-Anweisungen festgestellt, welches Vorzeichen die Variablen haben. Versuchen Sie fĂŒr die beiden nachfolgenden Quelltexte die Frage "Was weiß man hier ĂŒber x?" zu beantworten:

// die int-Variablen x und y werden deklariert und ĂŒber die Konsole eingegeben
if (x > 0)
    cout << " x ist positiv ";
    if (y > 0)
    else
        // Was weiss man hier ĂŒber x??
// die int-Variablen x und y werden deklariert und ĂŒber die Konsole eingegeben
if (x > 0)
    cout << " x ist positiv ";
    if (y > 0)
else
    // Was weiss man hier ĂŒber x??

Die beiden Quelltexte sind identisch — bis auf die unterschiedliche EinrĂŒckung bei else.

  • Die erste Variante suggeriert die Lesart, dass x immer noch positiv ist und mit else der Fall y ≀ 0 erzeugt wird.
  • Die zweite Variante suggeriert, dass jetzt x ≀ 0 ist.

Wie liest der Compiler diese Anweisung? Klar ist, dass er nicht auf die EinrĂŒckung achtet: Leerzeichen werden vom Compiler nur als Trennungszeichen gelesen, mit ihnen können aber niemals Anweisungen erzeugt oder verĂ€ndert werden.

Die Lösung des Problems ergibt sich sofort, wenn man das Block-Konzept beachtet, das in der folgenden Abbildung fĂŒr die Bedingung und Alternative nochmal erklĂ€rt wird.

Abbildung 1: Zur ErlÀuterung des dangling else Problems.Abbildung 1: Zur ErlÀuterung des dangling else Problems.

In Abbildung 1 sind links eine Bedingungs-PrĂŒfung und eine Alternative (beide gelb hinterlegt) zu sehen. Soll in einen dieser beiden Blöcke eine Alternative eingebaut werden (Block rechts mit unterstrichenem if-else), so muss an Stelle einer Anweisung der Block eingefĂŒgt werden. Aber egal wie man dies macht, es wird immer das unterstrichene else nahe dem unterstrichenen if stehen. Beachtet man dieses Block-Konzept, kann man niemals das unterstrichene else als zu dem nicht unterstrichenen if gehörig ansehen.

Und daher ist in oben gestelltem Problem klar, wie der Compiler den (doppelt aufgefĂŒhrten) Quelltext interpretieren muss: Das else gehört zu dem am nĂ€chsten gelegenen if und der Quelltext ist so zu lesen wie es die erste Darstellung (EinrĂŒckung beachten) suggeriert.

Das Problem des dangling else ist also weniger ein Problem von if else sondern vielmehr ein generelles Problem, wie Blöcke ineinandergeschachtelt werden können. Wie das Beispiel aus Abbildung 1 zeigt, ist es verwandt mit dem Problem, wie man Klammern in einem mathematischen Ausdruck setzen darf. So ist etwa

(5 + [3 + 8] + 2)

ein erlaubter Ausdruck. Dagegen dĂŒrfen die Klammern nicht wie folgt gesetzt werden:

(5 + [3 + 8) + 2]

In diesem Beispiel mag es noch erlaubt sein, da man die Klammern sowieso alle weglassen kann. Aber wie soll man folgenden Ausdruck interpretieren:

(5 + 2 * [3 + 8) + 2]

Der bedingte Operator

Die Alternative wird meist in der Form verwendet:

if (Bedingung)
    Anweisung;
else
    Anweisung;

wie in folgendem Beispiel:

if (x > 0)
    cout << " x ist positiv " << endl;
else
    cout << " x ist negativ " << endl;

DafĂŒr gibt es auch eine Kurzschreibweise, nĂ€mlich den sogenannten bedingten Operator:

x > 0 ? (cout << " x ist positiv " << endl) : (cout << " x ist negativ " << endl);

FĂŒr den AnfĂ€nger ist die Konstruktion mit if-else leichter zu lesen. Damit auch die Anweisung mit dem bedingten Operator leicht zu lesen ist, empfiehlt es sich Klammern zu setzen — auch an Stellen, an denen sie eigentlich ĂŒberflĂŒssig sind. Und schreiben Sie vor und nach jedem Operator ein Leerzeichen — gerade der Doppelpunkt ist sonst schwer zu erkennen, wie etwa in:

x>0 ? (cout<<" x ist positiv "<<endl):(cout<<" x ist negativ "<<endl);  // Nicht zu empfehlen: schwer lesbar aufgrund fehlender Leerzeichen

Vielleicht hilft folgende Lesart:

  • Das Fragezeichen soll darauf hindeuten, dass die Bedingung x > 0 abgefragt wird.
  • Falls die Bedingung erfĂŒllt ist, wird die Anweisung ausgefĂŒhrt, die sofort nach dem Fragezeichen kommt.
  • Ist die Bedingung nicht erfĂŒllt, wird die Anweisung nach dem Doppelpunkt ausgefĂŒhrt.

Und wenn das noch nicht hilft, kann man den bedingten Operator auch als eine ĂŒber drei Zeilen gehende Anweisung schreiben, die mehr an Falls — dann — sonst erinnert:

x > 0 ?
    (cout << " x ist positiv " << endl) : 
    (cout << " x ist negativ " << endl);

Der Nachteil hier ist, dass man den Doppelpunkt leicht mit einem Strichpunkt verwechseln kann.

Die Mehrfachalternative

Eine Mehrfachalternative kann immer aus verschachtelten Alternativen aufgebaut werden, es gibt aber auch die Möglichkeit der sogenannten switch-Anweisung. Dabei können beliebig viele FĂ€lle untersucht werden. Aber um dies zu realisieren, reicht es nicht mehr wie bei Falls — dann — sonst eine Bedingung anzugeben (also eine Bedingung vom Datentyp bool), die entweder eintritt oder nicht. FĂŒr die Mehrfachalternative wird eine Variable von ganzzahligem Datentyp angegeben, und die möglichen Bedingungen werden mit Hilfe der Werte dieser Variable definiert. Erlaubt sind also Zeichen, Zahlen wie short und int, aber keine Gleitkommazahlen.

Das folgende Beispiel untersucht, ob ein gegebenes Zeichen eine der Hexadezimal-Ziffern A, B, ..., F (beziehungsweise a, b, ..., f) ist und gibt es als Dezimalzahl aus.

char c;
cout << "Bitte geben Sie ein Zeichen ein: ";
cin >> c;

switch ( c )
    {
    case 'A':
    case 'a':
        cout << "10" << endl;
        break;                  // Verlassen der switch-Anweisung fĂŒr c == 'A' oder c == 'a'
    case 'B':
    case 'b':
        cout << "11" << endl;
        break;
    case 'C':
    case 'c':
        cout << "12" << endl;
        break;
    case 'D':
    case 'd':
        cout << "13" << endl;
        break;
    case 'E':
    case 'e':
        cout << "14" << endl;
        break;
    case 'F':
    case 'f':
        cout << "15" << endl;
        break;
    default:                        // wird ausgefĂŒhrt, wenn keine der Bedingungen (case .. ) eingetreten ist
        cout << "Das Zeichen war keine Hexadezimal-Ziffer zwischen A und F!";
        break;
    }
    cout << "Danke!" << endl;

Eigenschaften der Mehrfachalternative:

  1. Die Mehrfachalternative wird mit dem SchlĂŒsselwort switch eingeleitet. In den runden Klammern steht ein Ausdruck von ganzzahligem Datentyp: Man nennt ihn den Kontroll-Ausdruck. (Ein Gegenbeispiel mit Gleitkommazahlen folgt unten). Üblicherweise ist der Kontroll-Ausdruck eine Variable, deren Wert in den folgenden Anweisungen abgefragt wird.
  2. Es kann beliebig viele FĂ€lle (case) geben (Zeile 7, 8, 11, ...).
  3. Es kann aber höchstens eine default-Anweisung geben (Zeile 31). Mit default-Anweisung ist gemeint, dass dieser Fall ausgefĂŒhrt wird, wenn keine andere Bedingung zutreffend war. Die default-Anweisung muss nicht am Ende stehen, entsprechend ihrer inhaltlichen Bedeutung ist dies aber empfehlenswert.
  4. Die break-Anweisung sorgt dafĂŒr, dass die switch-Anweisung verlassen wird (Sprung zu Zeile 35). Wird break nicht eingesetzt, wird die nĂ€chste Möglichkeit (nĂ€chstes case) abgefragt. Da die Mehrfachalternative ĂŒblicherweise im Sinne eines entweder oder eingesetzt wird, ist dies nicht sinnvoll und beansprucht unnötige Rechenzeit.
  5. Nach case folgt der konstante Ausdruck (keine Variable), der den entsprechende Fall spezifiziert. Dieser konstante Ausdruck muss von ganzzahligem Datentyp sein. Bei der Abarbeitung der Mehrfachalternative wird dieser konstante Ausdruck mit dem Kontroll-Ausdruck (nach switch) verglichen. (Ein Gegenbeispiel, das eine Variable anstelle eines konstanten Ausdrucks einsetzt, folgt unten.)
  6. Nach case <Ausdruck> folgt ein Doppelpunkt und dann die entsprechende Anweisung, die in diesem Fall ausgefĂŒhrt werden soll. Es kann auch sofort das nĂ€chste case folgen (siehe Zeile 8), wodurch man die beiden FĂ€lle mit dem logischen ODER vereinigt hat.
  7. Sollen bei einer Bedingung mehrere Anweisungen ausgefĂŒhrt werden, ist es nicht nötig sie in {} einzuschließen; abgearbeitet werden alle Anweisungen bis zum nĂ€chsten case.
  8. Die default-Anweisung sollte immer eingesetzt werden: damit hat man die Möglichkeit, unerwartete oder unsinnige FÀlle abzufangen.

Das folgende Beispiel zeigt, dass man die switch-Anweisung nicht mit Gleitkommazahlen realisieren kann (Zeile 3 erzeugt den ersten Compiler-Fehler, obwohl die möglichen FÀlle mit ganzen Zahlen definiert werden):

float x {17};

switch ( x )        // "error: quantity is not an integer"
case 17:
    cout << x << endl;
case 0:
    cout << "0" << endl;
default:
    cout << "weder 17 noch 0" << endl;

SpĂ€ter wird noch gezeigt, dass neben ganzzahligen AusdrĂŒcken in switch auch Enumerationen erlaubt sind, um Mehrfachalternativen zu bilden.

Oben wurde gesagt, dass die möglichen Bedingungen der Mehrfachalternative mit Hilfe von konstanten AusdrĂŒcken gebildet werden mĂŒssen. Dazu das Gegenbeispiel, das zu einem Compiler-Fehler fĂŒhrt (Zeile 6):

short i {17};
short k {17};

switch (i)
{
case k:         // "error: the value of 'k' is not usable in a constant expression"
                // obwohl i == k
    cout << "i = k" << endl;
case 0:
    cout << "0" << endl;
default:
    cout << "weder 17 noch 0" << endl;
}

Aufgaben

1. Sortieren von Zahlen:

Der Nutzer soll drei Zahlen zwischen 0 und 1000 eingeben.

Falls eine Zahl (oder mehrere Zahlen) dies nicht erfĂŒllt, sollen die falschen Zahlen ausgegeben werden; der Nutzer wird aufgefordert, die entsprechenden Zahlen erneut einzugeben.

Falls jetzt die drei Zahlen immer noch nicht zwischen 0 und 1000 liegen, bricht das Programm ab.

Andernfalls sollen die drei Zahlen der GrĂ¶ĂŸe nach sortiert ausgegeben werden.

2. NĂ€herungsweise Berechnung der Sinus- und Kosinusfunktion:

Der Nutzer soll gefragt werden, ob er den Winkel im Bogen- oder im Winkelmaß eingeben möchte. Anschließend soll er den Winkel eingeben.

Das Programm soll Sinus- und Kosinusfunktion des Winkels durch folgende NĂ€herung berechnen (die Berechnungen werden im Bogenmaß durchgefĂŒhrt):

  • Zuerst wird die Berechnung der Winkelfunktion auf einen Winkel x im Intervall [0; π / 4] zurĂŒckgefĂŒhrt.
  • FĂŒr x ∈ [0; π / 4] kann folgende NĂ€herung verwendet werden (Taylor-Reihe):

sin x ≈ x - x3 / 3 + x5 / 5

cos x ≈ 1 - x2 / 2 + x4 / 4

Ausgegeben werden sollen die NĂ€herungen fĂŒr sin x und cos x sowie die exakten Werte (berechnet mit den Funktionen aus der Standard-Bibliothek).

3. Zwei Gleichungen mit zwei Unbekannten:

Gegeben ist ein Gleichungssystem der Form:

a1x + b1y = c1

a2x + b2y = c2

mit gegebenen Koeffizienten a1, a2, b1, b2, c1, c2 und gesuchten x, y.

  • Geben Sie an, wie viele Elemente die Lösungsmenge des Gleichungssystem hat.
  • Berechnen Sie die Lösungsmenge.

Schreiben Sie ein C++-Programm, dem die Koeffizienten eingegeben werden können und das anschließend die Berechnungen durchfĂŒhrt und die Ergebnisse ausgibt.