„Ein Programm ist eigentlich nichts weiter als eine Folge von Befehlen, die nacheinander ausgeführt werden. Mächtig wird ein Programm erst dadurch, dass es in Abhängigkeit von einer Bedingung entscheiden kann, ob der eine oder ein anderer Anweisungsblock ausgeführt werden soll.“
In diesem Kapitel meines C/C++ Tutorials lernst du,
- was Anweisungen sind,
- was Blöcke sind,
- was Ausdrücke sind,
- wie man auf der Basis von Bedingungen den Code verzweigt,
- was Wahrheitswerte sind und wie man darauf reagiert.
Anweisungen
In C++ steuern Anweisungen die Reihenfolge der Ausführung, werten Ausdrücke aus oder bewirken nichts (die Leeranweisung). Alle C++-Anweisungen enden mit einem Semikolon (;
), auch die Leeranweisung, die nur aus einem Semikolon besteht.
Eine häufig gebrauchte einfache Anweisung ist die Zuweisung:
x = a + b;
Diese Anweisung bedeutet im Gegensatz zur Algebra nicht x
gleich a + b
, sondern ist wie folgt zu interpretieren: »Weise den Wert der Summe aus a
und b
an x
zu.« Auch wenn diese Anweisung zwei Dinge bewirkt, handelt es sich um eine einzige Anweisung und hat daher nur ein Semikolon. Der Zuweisungsoperator nimmt die Zuweisung des auf der rechten Seite stehenden Ausdrucks an die linke Seite vor.
Whitespace
Leerzeichen gehören zusammen mit Tabulatoren und den Zeilenvorschüben zu den sogenannten Whitespace-Zeichen. Sie werden im allgemeinen in den Anweisungen ignoriert. Die oben behandelte Zuweisung lässt sich auch wie folgt schreiben:
x=a+b;
oder
x =a
+ b ;
Die zweite Variante ist zwar zulässig, aber kompletter Blödsinn. Durch Whitespace- Zeichen sollen Programme leichter zu lesen und zu warten sein. Man kann damit aber auch einen unleserlichen Code produzieren. C++ stellt die Möglichkeiten bereit, für den sinnvollen Einsatz ist der Programmierer verantwortlich.
Whitespace-Zeichen (Leerzeichen, Tabulatoren und Zeilenvorschübe) sind nicht sichtbar. Werden diese Zeichen gedruckt, bleibt das Papier weiß (white).
Anweisungsblöcke und Verbundanweisungen
An allen Stellen, wo eine einzelne Anweisung stehen kann, ist auch eine Verbundanweisung (auch Block genannt) zulässig. Ein Block beginnt mit einer öffnenden geschweiften Klammer ({
) und endet mit einer schließenden geschweiften Klammer (}
).
In einem Block ist zwar jede Anweisung mit einem Semikolon abzuschließen, der Block selbst endet aber nicht mit einem Semikolon. Dazu ein Beispiel:
{
temp = a;
a = b;
b = temp;
}
Dieser Codeblock tauscht die Werte der Variablen in a
und b
aus.
Ausdrücke
Alles, was zu einem Wert ausgewertet werden kann, nennt man in C++ einen Ausdruck . Von einem Ausdruck sagt man, dass er einen Wert zurückliefert. Demzufolge ist die Anweisung 3 + 2;
, die den Wert 5
zurückliefert, ein Ausdruck. Alle Ausdrücke sind auch Anweisungen.
Die Unzahl der Codeabschnitte, die sich als Ausdruck entpuppen, mag dich vielleicht überraschen. Hier drei Beispiele:
3.2 // liefert den Wert 3.2
PI // float-Konstante, die den Wert 3.14 zurückgibt
SekundenProMinute // int-Konstante, die 60 liefert
Vorausgesetzt, daß PI
eine Konstante mit dem Wert 3.14
und SekundenProMinute
eine Konstante mit dem Wert 60
ist, stellen alle drei Anweisungen gültige Ausdrücke dar.
Der Ausdruck
x = a + b;
addiert nicht nur a
und b
und weist das Ergebnis an x
zu, sondern liefert auch den Wert dieser Zuweisung (den Wert in x
). Daher ist diese Anweisung ebenfalls ein Ausdruck und kann somit auch auf der rechten Seite eines Zuweisungsoperators stehen:
y = x = a + b;
Diese Zeile wird in der folgenden Reihenfolge ausgewertet:
Addiere a
zu b
.
Weise das Ergebnis des Ausdrucks a + b
an x
zu.
Weise das Ergebnis des Zuweisungsausdrucks x = a + b
an y
zu.
Wenn a
, b
, x
und y
ganze Zahlen sind, a
den Wert 2 und b
den Wert 5 hat, enthält sowohl x
als auch y
nach Ausführung dieser Anweisung den Wert 7.
Das Listing „expressions.cpp“ demonstriert die Auswertung komplexer Ausdrücke:
// Listing: expressions.cpp
// demonstriert die Auswertung
// komplexer Ausdrücke
#include <iostream>
using namespace std;
int main(void)
{
int a=0, b=0, x=0, y=35;
cout << "a: " << a << " b: " << b;
cout << " x: " << x << " y: " << y << endl;
a = 9;
b = 7;
y = x = a+b;
cout << "a: " << a << " b: " << b;
cout << " x: " << x << " y: " << y << endl;
return 0;
}
Ausgabe:
a: 0 b: 0 x: 0 y: 35
a: 9 b: 7 x: 16 y: 16
Analyse:
- Zeile 10 deklariert und initialisiert die vier Variablen.
- Die Ausgabe ihrer Werte erfolgt in den Zeilen 12 und 13.
- Zeile 15 weist den Wert 9 an die Variable
a
zu. - Zeile 16 weist den Wert 7 an die Variable
b
zu. - Zeile 17 summiert die Werte von
a
und b
und weist das Ergebnis x
zu. Dieser Ausdruck (x = a + b
) ergibt einen Wert (die Summe aus a
und b
), der wiederum y
zugewiesen wird.
Operatoren
Ein Operator ist ein Symbol, das den Compiler zur Ausführung einer Aktion veranlasst. Operatoren verarbeiten Operanden, und in C++ sind alle Operanden Ausdrücke. In C++ gibt es mehrere Arten von Operatoren. Zwei Arten von Operatoren sind:
- Zuweisungsoperatoren
- mathematische Operatoren
Zuweisungsoperator
Der Zuweisungsoperator (=
) bewirkt, dass der Operand auf der linken Seite des Operators den Wert von der rechten Seite des Operators erhält. Der Ausdruck
x = a + b;
weist dem Operanden x
den Wert zu, der als Ergebnis der Addition von a
und b
entsteht.
Einen Operanden, der auf der linken Seite eines Zuweisungsoperators zulässig ist, bezeichnet man als L-Wert (linker Wert). Ein entsprechender Operand auf der rechten Seite heißt R-Wert.
Konstanten sind R-Werte und können nicht als L-Werte vorkommen. Demzufolge ist die Anweisung
x = 35; // OK
zulässig, während die Anweisung
35 = x; // Fehler, kein L-Wert!
nicht erlaubt ist.
Ein L-Wert ist ein Operand, der auf der linken Seite eines Ausdrucks stehen kann. Als R-Wert bezeichnet man einen Operanden, der auf der rechten Seite eines Ausdrucks vorkommen kann. Während alle L-Werte auch als R-Werte zulässig sind, dürfen nicht alle R-Werte auch als L-Werte verwendet werden. Ein Literal ist zum Beispiel ein R- Wert, der nicht als L-Wert erlaubt ist. Demzufolge kann man x = 5;
schreiben, 5 = x;
jedoch nicht (x
kann ein R- und eine L-Wert sein, 5
hingegen ist nur ein R-Wert).
Mathematische Operatoren
C/C++++ kennt die fünf mathematischen Operatoren Addition (+
), Subtraktion (-
), Multiplikation (*
), Division (/
) und Modulo-Division (%
).
Die zwei Grundrechenarten Addition und Subtraktion arbeiten wie gewohnt, wenn es auch bei der Subtraktion mit vorzeichenlosen Ganzzahlen zu überraschenden Ergebnissen kommen kann, falls das Ergebnis eigentlich negativ ist. Einen Eindruck davon hast du ja schon erhalten, als der Variablenüberlauf beschrieben wurde. Im Listing „suboverflow.cpp“ kannst du nachvollziehen was passiert, wenn du eine große Zahl eines unsigned
-Typs von einer kleinen Zahl eines unsigned
-Typs subtrahierst:
// Listing: suboverflow.cpp
// Subtraktion und Integer-Überlauf
#include <iostream>
using namespace std;
int main(void)
{
unsigned int difference;
unsigned int bigNumber = 100;
unsigned int smallNumber = 50;
// kein Überlauf
difference = bigNumber - smallNumber;
cout << "Die Differenz betraegt: " << difference;
// Überlauf
difference = smallNumber - bigNumber;
cout << "\nJetzt betraegt die Differenz: " << difference <<endl;
return 0;
}
Ausgabe:
Die Differenz betraegt: 50
Jetzt betraegt die Differenz: 4294967246
Analyse:
- Zeile 14 ruft den Subtraktions-Operator auf, und das Ergebnis, das ganz unseren Erwartungen entspricht, wird in Zeile 15 ausgegeben.
- Zeile 18 ruft den Subtraktions- Operator erneut auf. Diesmal wird jedoch eine große
unsigned
-Zahl von einer kleinen unsigned
-Zahl subtrahiert. Das Ergebnis wäre eigentlich negativ, doch da es als unsigned
-Zahl ausgewertet (und ausgegeben) wird, ist das Ergebnis ein Überlauf. Auf dieses Problem wird noch genauer eingegangen, wenn wir über Operator- Vorrang sprechen.
Integer-Division und Modulo
Die Integer-Division unterscheidet sich etwas von der gewohnten Division. Um genau zu sein: Integer-Division entspricht dem, was du in der zweiten Klasse gelernt habst. Dividiert man 21
durch 4
und betrachtet das Ganze als Integer-Division (wie in der Grundschule mit sieben Jahren) ist das Ergebnis 5
Rest 1
.
Um den Rest zu erhalten, bedient man sich des Modulo-Operators (%
) und berechnen 21
modulus 4
(21 % 4
) mit dem Ergebnis 1. Der Modulo-Operator gibt den Rest einer Ganzzahldivision zurück.
Die Berechnung des Modulus kann recht nützlich sein, wenn du zum Beispiel eine Anweisung bei jeder zehnten Aktion drucken willst. Jede Zahl, deren Modulus 0
ergibt, wenn du deren Modulus mit 10
berechnest, ist ein Mehrfaches von 10
. So ist 1 %
10
gleich 1
, 2 % 10
gleich 2
und so weiter, bis 10 % 10
den Wert 0
ergibt. 11 % 10
ist erneut 1
und dieses Muster setzt sich bis zum nächsten Mehrfachen von 10
, das heißt 20
, fort. Diese Technik kommt zur Anwendung, wenn wir später im Detail auf die Schleifen zu sprechen kommen.
Frage: Wenn ich 5
durch 3
teile, erhalte ich 1
. Was mache ich falsch?
Antwort: Wenn du einen Integerwert durch einen Anderen teilst, dann erhältst du auch einen Integerwert als Ergebnis. Und im Falle von 5/3
ist dies eben 1
.
Um als Rückgabewert eine Bruchzahl zu erhalten, musst du Fließkommazahlen vom Typ float
oder double
verwenden.
5,0/3,0
ergibt als Bruchzahl: 1,66667
Falls deine Methode Integer als Parameter übernimmt, musst du diese in Fließkommazahlen von Typ float
oder double
umwandeln.
Um den Typ der Variablen zu ändern, musst du eine Typumwandlung (cast
) vornehmen. Damit teilst du dem Compiler im wesentlichen mit, dass du weißt was du tust – und das solltest du auch, denn der Compiler wird sich deinem Willen nicht widersetzen.
In diesem speziellen Fall teilest du dem Compiler mit: »Ich weiß, du denkst, dies ist ein Integer, aber ich weiß, was ich tue, darum behandle diesen Wert als Fließkommazahl«.
Für die Typumwandlung kannst du noch die alte C-Methode oder den neueren vom ANSI-Standard empfohlenen Operator static_cast
verwenden. Ein Beispiel dazu findst du im Listing „typecast.cpp“:
// Listing: typecast.cpp
// Typumwandlung von int nach float
#include <iostream>
using namespace std;
// Funktionsprototypen
void intDiv(int x, int y);
void floatDiv(int x, int y);
// Funktion: void intDiv(int x, int y)
void intDiv(int x, int y)
{
int z = x / y;
cout << "z: " << z << endl;
}
// Funktion: void floatDiv(int x, int y)
void floatDiv(int x, int y)
{
float a = (float)x; // Typecast: alter Stil
float b = static_cast<float>(y); // vorzuziehender Stil
float c = a / b;
cout << "c: " << c << endl;
}
int main(void)
{
int x = 5, y = 3;
intDiv(x,y);
floatDiv(x,y);
return 0;
}
Ausgabe:
z: 1
c: 1.66667
Analyse:
- Die Zeilen 8 und 9 definieren die Funktionsprototypen der beiden selbst definierten Funktionen.
- Die Zeilen 12 bis 16 definieren die Funktion (Methode) intDiv(). Diese druckt einen Integerwert aus.
- Die Zeilen 19 bis 26 definieren die Funktion(Methode) floatDiv(). Diese druckt einen Gleitkommawert (float) aus.
- In Zeile 21 wird der Variablen x per Typecast im alten Stil der Datentyp float zugeordnet und deren Wert der Gleitkommavariablen a übergeben.
- In Zeile 22 wird der Variablen y per Typecast im neuen Stil der Datentyp float zugeordnet und deren Wert der Gleitkommavariablen b übergeben.
- In Zeile 23 wird der Gleitkommavariablen c das Ergebnis der Berechnung aus a geteilt durch b übergeben.
- Zeile 30 deklariert zwei lokale Integer-Variablen innerhalb von main().
- Als Parameter werden in Zeile 32
intDiv
und in Zeile 33 floatDiv
übergeben.
Zusammengesetzte Operatoren
Häufig muss man einen Wert zu einer Variablen addieren und dann das Ergebnis an dieselbe Variable zuweisen. Im folgenden Beispiel wird der Wert der Variablen meinAlter
um 2 erhöht:
int meinAlter = 5;
int temp;
temp = meinAlter + 2; // 5 und 2 addieren und in temp ablegen
meinAlter = temp; // nach meinAlter zurückschreiben
Dieses Verfahren ist allerdings recht umständlich. In C++ kann man dieselbe Variable auf beiden Seiten des Zuweisungsoperators einsetzen und das obige Codefragment eleganter formulieren:
meinAlter = meinAlter +2;
In der Algebra wäre dieser Ausdruck unzulässig, während ihn C++ als »addiere 2 zum Wert in meinAlter
, und weise das Ergebnis an meinAlter
zu« interpretiert.
Das Ganze läßt sich noch einfacher schreiben, auch wenn es im ersten Moment etwas unverständlich aussieht:
meinAlter += 2;
Der zusammengesetzte Additionsoperator (+=
) addiert den R-Wert zum L-Wert und führt dann eine erneute Zuweisung des Ergebnisses an den L-Wert durch. Der Operator heißt »Plus-Gleich«. Die Anweisung ist dann als »meinAlter
plus gleich 2« zu lesen. Wenn meinAlter
zu Beginn den Wert 4 enthält, steht nach Ausführung dieser Anweisung der Wert 6 in meinAlter
.
Weitere zusammengesetzte Operatoren gibt es für Subtraktion (-=
), Division (/=
), Multiplikation (*=
) und Modulo-Operation (%=
).
Inkrementieren und Dekrementieren
Am häufigsten hat man den Wert 1 zu einer Variablen zu addieren (bzw. zu subtrahieren). In C++ spricht man beim Erhöhen des Wertes um 1 von Inkrementieren und beim Verringern um 1 von Dekrementieren. Für diese Operationen stehen spezielle Operatoren bereit.
Der Inkrement-Operator (++
) erhöht den Wert der Variablen um 1, der Dekrement- Operator (--
) verringert ihn um 1. Möchte man die Variable C inkrementieren, schreibt man die folgende Anweisung:
C++; // Beginne mit C und inkrementiere den enthaltenen Wert.
Diese Anweisung ist äquivalent zur ausführlicher geschriebenen Anweisung
C = C + 1;
Das gleiche Ergebnis liefert die verkürzte Darstellung
C += 1;
Präfix und Postfix
Sowohl der Inkrement-Operator (++
) als auch der Dekrement-Operator (--
) existieren in zwei Spielarten: Präfix und Postfix. Die Präfix-Version wird vor den Variablennamen geschrieben (++mein Alter
), die Postfix-Version danach (mein Alter++
).
In einer einfachen Anweisung spielt es keine Rolle, welche Version man verwendet. In einer komplexen Anweisung ist es allerdings entscheidend, ob man eine Variable zuerst inkrementiert (oder dekrementiert) und dann das Ergebnis einer anderen Variablen zuweist. Der Präfix-Operator wird vor der Zuweisung ausgewertet, der Postfix- Operator nach der Zuweisung.
Die Semantik von Präfix bedeutet: Inkrementiere den Wert und übertrage ihn dann. Die Bedeutung des Postfix-Operators lautet dagegen: Übertrage den Wert und inkrementiere das Original.
Das folgende Beispiel verdeutlicht diese Vorgänge. Es sei angenommen, dass x
eine ganze Zahl mit dem Wert 5 ist. Bei der Anweisung
int a = ++x;
inkrementiert der Compiler den Wert in x
(und macht ihn damit zu 6), holt dann diesen Wert und weist ihn an a
zu. Daher ist jetzt sowohl a
als auch x
gleich 6.
Schreibt man anschließend
int b = x++;
weist man den Compiler an, den Wert in x
(6) zu holen, ihn an b
zuzuweisen und dann den Wert von x
zu inkrementieren. Demzufolge ist nun b
gleich 6, während x
gleich 7 ist. Das Listing „increment.cpp“ zeigt Verwendung und Besonderheiten beider Typen:
// Listing: increment.cpp
// demonstriert die Verwendung der
// Inkrement- u. Dekrement-Operatoren
// in Prä- und Postfix-Notation
#include <iostream>
using namespace std;
int main(void)
{
int meinAlter = 39; // initialisiert zwei Integer-Variablen
int deinAlter = 39;
cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
cout << "Du bist: " << deinAlter << " Jahre alt\n";
meinAlter++; // Postfix-Inkrement
++deinAlter; // Präfix-Inkrement
cout << "Ein Jahr ist vergangen...\n";
cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
cout << "Du bist: " << deinAlter << " Jahre alt\n";
cout << "Noch ein Jahr ist vergangen\n";
cout << "Ich bin: " << meinAlter++ << " Jahre alt.\n";
cout << "Du bist: " << ++deinAlter << " Jahre alt\n";
cout << "Und noch einmal ausgeben.\n";
cout << "Ich bin: " << meinAlter << " Jahre alt.\n";
cout << "Du bist: " << deinAlter << " Jahre alt\n";
return 0;
}
Ausgabe:
Ich bin: 39 Jahre alt.
Du bist: 39 Jahre alt
Ein Jahr ist vergangen...
Ich bin: 40 Jahre alt.
Du bist: 40 Jahre alt
Noch ein Jahr ist vergangen
Ich bin: 40 Jahre alt.
Du bist: 41 Jahre alt
Und noch einmal ausgeben.
Ich bin: 41 Jahre alt.
Du bist: 41 Jahre alt
Analyse:
- Die Zeilen 11 und 12 deklarieren zwei Integer-Variablen und initialisieren sie jeweils mit dem Wert 39. Die Ausgabe der Werte findet in den Zeilen 14 und 15 statt.
- Zeile 17 inkrementiert
mein Alter
mit dem Postfix-Operator, und Zeile 18 inkrementiert dein Alter
mit dem Präfix-Operator. Die Ergebnisse werden in den Zeilen 21 und 22 ausgegeben und sind beide identisch (beide 40). - In Zeile 24 wird
mein Alter
mit dem Postfix-Operator als Teil einer Ausgabeanweisung inkrementiert. Durch den Postfix-Operator erfolgt die Inkrementierung nach der Ausgabe, und es erscheint auch hier in der Ausgabe der Wert 40. Im Gegensatz dazu inkrementiert Zeile 25 die Variable dein Alter
mit dem Präfix-Operator. Das Inkrementieren findet jetzt vor der Ausgabe statt, und es erscheint der Wert 41 in der Anzeige. - Schließlich werden die Werte in den Zeilen 27 und 28 erneut ausgegeben. Da die Inkrement-Anweisung vollständig abgearbeitet ist, lautet der Wert in
mein Alter
jetzt 41, genau wie der Wert in dein Alter
.
Rangfolge der Operatoren
Welche Operation wird in der komplexen Anweisung
x = 5 + 3 * 8;
zuerst ausgeführt, die Addition oder die Multiplikation? Führt man die Addition zuerst aus, lautet das Ergebnis 8 * 8 oder 64. Bei vorrangiger Multiplikation heißt das Ergebnis 5 + 24 oder 29.
Jeder Operator besitzt einen festgelegten Vorrang. Eine vollständige Liste der Operatorprioritäten findest du in der Tabelle „Priorität von Operatoren“. Die Multiplikation hat gegenüber der Addition höhere Priorität. Der Wert des Ausdrucks ist demnach 29.
Wenn zwei mathematische Operatoren gleichrangig sind, werden sie in der Reihenfolge von links nach rechts ausgeführt. Demzufolge wird in
x = 5 + 3 + 8 * 9 + 6 * 4;
die Multiplikation zuerst, von links nach rechts, ausgeführt. Es ergeben sich die beiden Terme 8*9 = 72 und 6*4 = 24. Damit wird der Ausdruck zu
x = 5 + 3 + 72 + 24;
Nun kommt noch die Addition von links nach rechts 5 + 3 = 8, 8 + 72 = 80, 80 + 24 = 104.
Die Rangfolge ist unbedingt zu beachten. Bestimmte Operatoren wie der Zuweisungsoperator werden von rechts nach links ausgeführt. Was passiert nun, wenn die Rangfolge nicht deinen Vorstellungen entspricht? Sieh dir dazu folgenden Ausdruck an:
SekundenGsamt = MinutenNachdenken + MinutenTippen * 60
In diesem Ausdruck soll nicht MinutenTippen
zuerst mit 60 multipliziert und dann zu MinutenNachdenken
addiert werden. Beabsichtigt ist, zuerst die Addition der beiden Variablen durchzuführen, um die Summe der Minuten zu ermitteln und anschließend diese Zahl mit 60 zu multiplizieren, um die Anzahl der Sekunden zu berechnen.
In diesem Fall setzt man Klammern, um die Rangfolge zu ändern. Elemente in Klammern werden mit einer höheren Priorität ausgeführt als irgendein mathematischer Operator. Das gewünschte Ergebnis erhält man also mit
SekundenGesamt = (MinutenNachdenken + MinutenTippen) * 60
Verschachtelte Klammern
Bei komplexen Ausdrücken sind eventuell Klammern zu verschachteln. Beispielsweise ist die Anzahl der Sekunden zu berechnen und danach die Anzahl der Mitarbeiter, bevor die Multiplikation der Mitarbeiter mit den Sekunden erfolgt (um etwa die gesamte Arbeitszeit in Sekunden zu erhalten):
PersonenSekundenGesamt = ( ( (MinutenNachdenken + MinutenTippen) * 60)
* (Mitarbeiter + Beurlaubt) )
Dieser zusammengesetzte Ausdruck ist von innen nach außen zu lesen. Zuerst erfolgt die Addition von MinutenNachdenken
und MinutenTippen
, da dieser Ausdruck in den innersten Klammern steht. Anschließend wird diese Summe mit 60 multipliziert. Es schließt sich die Addition von Mitarbeiter
und Beurlaubt
an. Schließlich wird die berechnete Mitarbeiterzahl mit der Gesamtzahl der Sekunden multipliziert.
Dieses Beispiel weist auf einen wichtigen Punkt hin: Für einen Computer ist ein solcher Ausdruck leicht zu interpretieren, für einen Menschen ist er dagegen nur schwer zu lesen, zu verstehen oder zu modifizieren. Der gleiche Ausdruck in einer anderen Form mit Variablen zur Zwischenspeicherung sieht folgendermaßen aus:
MinutenGesamt = MinutenNachdenken + MinutenTippen;
SekundenGesamt = MinutenGesamt * 60;
MitarbeiterGesamt = Mitarbeiter + Beurlaubt;
PersonenSekundenGesamt = MitarbeiterGesamt * SekundenGesamt;
Dieses Beispiel verwendet zwar temporäre Variablen und erfordert mehr Schreibarbeit als das vorherige Beispiel, ist aber leichter zu verstehen. Füge am Beginn einen Kommentar ein, um die Absichten hinter diesem Codeabschnitt zu erläutern, und ändere die 60
in eine symbolische Konstante. Damit erhältst du einen Code, der leicht zu verstehen und zu warten ist.
Tipps zu Operatoren und verschachtelten Klammern:
- Denke daran, dass Ausdrücke einen Wert haben.
- Verwende den Präfix-Operator (++Variable), um die Variable zu inkrementieren oder zu dekrementieren, bevor sie in einem Ausdruck verwendet wird.
- Verwende den Postfix-Operator (Variable++), um die Variable zu inkrementieren oder zu dekrementieren, nachdem sie verwendet wurde.
- Verwende Klammern, um die Rangfolge bei der Abarbeitung der Operatoren zu ändern.
- Verschachtele deine Ausdrücke nicht zu sehr, da sie sonst schwer verständlich werden und die Wartung erschwert wird.
Wahrheitswerte
In früheren C++-Versionen wurden Wahrheitswerte durch Integerzahlen dargestellt. Die neueren ANSI-Standards haben einen neuen Datentyp eingeführt: bool
. Dieser Typ hat zwei mögliche Werte false
(unwahr) oder true
(wahr).
Jeder Ausdruck kann auf seinen Wahrheitsgehalt ausgewertet werden. Ausdrücke, die mathematisch gesehen eine Null ergeben, liefern false
zurück, alle anderen liefern true
zurück.
Vergleichsoperatoren
Mit den Vergleichsoperatoren (oder relationalen Operatoren) ermittelt man, ob zwei Zahlen gleich sind, oder ob eine größer oder kleiner als die andere ist. Jeder Vergleichsausdruck wird entweder zu true
oder zu false
ausgewertet. Zu den Vergleichsoperatoren gehören: Gleich (==
), Kleiner als (<
), Größer als (>
), Kleiner oder Gleich (<=
), Größer oder Gleich (>=
) und Ungleich (!=
). In der Tabelle „Bool’sche Vergleichsoperatoren“ sind die Vergleichsoperatoren zusammengefasst.
Ein Beispiel: Hat die Integer-Variable meinAlter
den Wert 39 und die Integer-Variable deinAlter
den Wert 40, kann man mit dem relationalen Operator »Gleich« prüfen, ob beide gleich sind:
meinAlter == deinAlter; // ist der Wert in meinAlter der gleiche wie
// in deinAlter?
Dieser Ausdruck ergibt 0 oder false
, da die Variablen nicht gleich sind. Der Ausdruck
meinAlter > deinAlter; // ist meinAlter größer als deinAlter?
liefert 0 oder false
.
Beachte: Viele Neueinsteiger in die C++-Programmierung verwechseln den Zuweisungsoperator (=
) mit dem Gleichheitsoperator (==
). Das kann zu bösartigen Fehlern im Programm führen.
Das Listing „bools.cpp“ zeigt die Verwendung des Gleichheitsoperators:
// Listing: bools.cpp
// Veranschaulichung von Wahrheitswerten
#include <iostream>
using namespace std;
int main(void)
{
int meinHund = 1, deinHund = 2, keinHund = meinHund;
cout << "TRUE = " << true << endl;
cout << "FALSE = " << false << endl;
if (meinHund == keinHund)
cout << "TRUE: Ich habe gar keinen Hund." << endl;
else
cout << "FALSE: Mein Hund heisst Larry." << endl;
if (meinHund == deinHund)
cout << "TRUE: Mein Hund heisst Larry." << endl;
else
cout << "FALSE: Dein Hund heisst Schnitzel." << endl;
return 0;
}
Ausgabe:
TRUE = 1
FALSE = 0
TRUE: Ich habe gar keinen Hund.
FALSE: Dein Hund heisst Schnitzel.
Analyse:
- Zeile 9 definiert drei Integer-Variablen, dabei wird dem Wert der ersten Variablen meinHund der Wert der dritten Variablen keinHund zugeordnet.
- Die Zeilen 11 und 12 geben die Werte für true und false aus Sicht des Compilers aus.
- Die Zeilen 14 bis 17 ermitteln per if – else Verzweigung unter Verwendung des Gleichheitsoperators, ob ich denn nun einen Hund habe oder nicht. Da die gespeicherten Werte von meinHund und keinHund identisch sind, evaluiert der Ausdruck
if(meinHund == keinHund)
zu true. - Die Zeilen 19 bis 21 ermitteln per if – else Verzweigung unter Verwendung des Gleichheitsoperators, ob mein Hund genauso heißt wie meiner. Da die gespeicherten Werte von meinHund und deinHund nicht identisch sind, evaluiert der Ausdruck
if(meinHund == deinHund)
zu false.
Die if-Anweisung
Wir haben die if-Anweisung im Verlauf des C++ Tutorials bereit mehrfach verwendet, ohne näher auf sie einzugehen. Das holen wir nun nach.
Normalerweise verläuft der Programmfluss zeilenweise in der Reihenfolge, in der die Anweisungen in Ihrem Quellcode stehen. Mit der if
-Anweisung kann man auf eine Bedingung testen (beispielsweise ob zwei Variablen gleich sind) und in Abhängigkeit vom Testergebnis zu unterschiedlichen Teilen des Codes verzweigen.
Die einfachste Form der if
-Anweisung sieht folgendermaßen aus:
if (Ausdruck)
Anweisung;
Der Ausdruck in den Klammern kann jeder beliebige Ausdruck sein, er enthält aber in der Regel einen der Vergleichsausdrücke. Wenn der Ausdruck den Wert false
liefert, wird die Anweisung übersprungen. Ergibt sich der Wert true
, wird die Anweisung ausgeführt. Sehen Sie sich dazu folgendes Beispiel an:
if (grosseZahl > kleineZahl)
grosseZahl = kleineZahl;
Dieser Code vergleicht grosseZahl
mit kleineZahl
. Wenn grosseZahl
größer ist, setzt die zweite Zeile den Wert von grosseZahl
auf den Wert von kleineZahl
.
Da ein von Klammern eingeschlossener Anweisungsblock einer einzigen Anweisung entspricht, kann die folgende Verzweigung ziemlich umfangreich und mächtig sein:
if (Ausdruck)
{
Anweisung1;
Anweisung2;
Anweisung3;
}
Zur Veranschaulichung ein einfaches Beispiel:
if (grosseZahl > kleineZahl)
{
grosseZahl = kleineZahl;
cout << "grosseZahl: " << grosseZahl << "\n ";
cout << "kleineZahl: " << kleineZahl << "\n ";
}
Diesmal wird für den Fall, dass grosseZahl
größer ist als kleineZahl
, nicht nur grosseZahl
auf den Wert von kleineZahl
gesetzt, sondern es wird auch eine Nachricht ausgegeben. Im Listing „branches.cpp“ findest du ein ausführlicheres Beispiel zu der Verzweigung auf der Grundlage von Vergleichsoperatoren.
// Listing: branches.cpp
// zeigt if-Anweisungen in Verbindung
// mit Vergleichsoperatoren
#include <iostream>
using namespace std;
int main(void)
{
int hsvScore, bmScore;
cout << "Gib den Punktestand des Hamburger SV ein: ";
cin >> hsvScore;
cout << "\nGib den Punktestand von Bayern Muenchen ein: ";
cin >> bmScore;
cout << "\n";
if (hsvScore > bmScore)
cout << "Vorwärts HSV!\n";
if (hsvScore < bmScore)
{
cout << "Zieht den Bayern die Lederhosen aus!\n";
cout << "Tolle Tage in der 2. Liga!\n";
}
if (hsvScore == bmScore)
{
cout << "Ein Gleichstand? Nee, das kann nicht sein.\n";
cout << "Gib den richtigen Punktestand der Bayern ein: ";
cin >> bmScore;
if (hsvScore > bmScore)
cout << "Ich wusste es! HSV bringt Fischbroetchen mit!";
if (bmScore > hsvScore)
cout << "Ich wusste es! Vorwärts mit Weisswurst!";
if (bmScore == hsvScore)
cout << "Wow, es war wirklich ein Gleichstand!";
}
cout << "\nDanke fuer die Nachricht.\n";
return 0;
}
Ausgabe:
bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./branches
Gib den Punktestand des Hamburger SV ein: 33
Gib den Punktestand von Bayern Muenchen ein: 33
Ein Gleichstand? Nee, das kann nicht sein.
Gib den richtigen Punktestand der Bayern ein: 8
Ich wusste es! HSV bringt Fischbroetchen mit!
Danke fuer die Nachricht.
Analyse
Dieses Programm fordert den Anwender auf, den Punktestand für zwei Fußballteams einzugeben. Die Punkte werden in Integer-Variablen abgelegt. Die Zeilen 19, 22 und 28 vergleichen dann diese Variablen mit Hilfe einer if
-Anweisung.
Ist eine Punktzahl höher als die andere, wird eine Nachricht ausgegeben. Liegt ein Punktegleichstand vor, wird der Codeblock von Zeile 29 bis Zeile 42 ausgeführt. Die zweite Punktezahl wird erneut abgefragt und die Punkte abermals verglichen.
Ist die Punktzahl der Bayern von Anfang an höher als die des HSV, wird die if
– Anweisung in Zeile 19 zu false
ausgewertet und Zeile 20 nicht aufgerufen. Der Vergleich in Zeile 19 ist demnach true
und die Anweisungen in den Zeilen 24 und 25 werden ausgeführt. Anschließend wird die if
-Bedingung in Zeile 28 überprüft und das Ergebnis ist folgerichtig (wenn Zeile 19 true
ergab) false
. Damit wird der ganze Codeblock bis Zeile 43 übersprungen.
In diesem Beispiel werden trotz einer if
-Anweisung mit dem Ergebnis true
auch die anderen if
-Anweisungen geprüft.
Beachte: Viele Neueinsteiger in die C++-Programmierung machen den Fehler, ein Semikolon an die if
-Anweisung anzuhängen:
if(EinWert < 10);
EinWert = 10;
Die Absicht obigen Codes ist, herauszufinden, ob EinWert
kleiner ist als 10 und wenn ja, die Variable auf 10 zu setzen, so dass 10 der Minimalwert für EinWert
ist. Bei Ausführung dieses Codefragments wirst du feststellen, dass EinWert
immer auf 10 gesetzt wird! Und warum? Die if
-Anweisung endet mit dem Semikolon (dem Tu-Nichts-Operator).
Denke daran, dass die Einrückung für den Compiler ohne Belang ist. Dies Fragment hätte korrekterweise auch so geschrieben werden können:
if (EinWert < 10) // pruefen
; // tue nichts
EinWert = 10; // zuweisen
Durch das Entfernen des Semikolons wird die letzte Zeile Teil der if
-Anweisung und der Code wird genau das ausführen, was du beabsichtigt hattest.
Einrückungsarten
Im Listing „branches.cpp“ konntest du eine der Möglichkeiten sehen, if
-Anweisungen einzurücken. Nichts jedoch ist besser geeignet, einen Glaubenskrieg heraufzubeschwören, als eine Gruppe von Programmierern zu fragen, wie man am besten Anweisungen in Klammern einrückt. Es gibt Dutzende von Möglichkeiten. Die am weitesten verbreiteten drei Möglichkeiten möchte ich dir hier vorstellen:
- Die öffnende Klammer steht direkt hinter der Bedingung und die schließende Klammer bündig mit dem
if
, um den Anweisungsblock zu schließen: // 1. Möglichkeit
if (Ausdruck){
Anweisungen
}
- Beide Klammern werden bündig mit dem
if
ausgerichtet und die Anweisungen werden eingerückt: // 2. Möglichkeit
if (Ausdruck)
{
Anweisungen
}
- Sowohl die Klammern als auch die Anweisungen werden eingerückt:
// 3. Möglichkeit
if (Ausdruck)
{
Anweisungen
}
In diesem Tutorial habe ich die zweite Alternative gewählt, da ich leichter feststellen kann, wo Anweisungsblöcke anfangen und enden, wenn die Klammern miteinander und mit der Bedingung bündig sind. Aber auch hier gilt: Es ist nicht wichtig, für welche Art der Einrückung du dich entscheidest, solange du konsequent bei der einmal gewählten Variante bleibst.
Die else-Klausel
Oftmals soll ein Programm bei erfüllter Bedingung (true
) den einen Zweig durchlaufen und bei nicht erfüllter Bedingung (false
) einen anderen. In Listing 4.5 wollten Sie eine Nachricht (Vorwärts Sox!
) ausgeben, wenn die erste Bedingung (RedSoxScore > Yankees
) erfüllt oder true
wird, und eine andere Nachricht (Vorwärts Yanks!
), wenn der Test false
ergibt.
Die bisher gezeigte Form, die zuerst eine Bedingung testet und dann die andere, funktioniert zwar, ist aber etwas umständlich. Das Schlüsselwort else
trägt hier zur besseren Lesbarkeit des Codes bei:
if (Ausdruck)
Anweisung;
else
Anweisung;
Das Listing „ifelse.cpp“ demonstriert die Verwendung des Schlüsselwortes else:
// Listing: ifelse.cpp
// zeigt die if-Anweisung mit
// der else-Klausel
#include <iostream>
using namespace std;
int main(void)
{
int firstNumber, secondNumber;
cout << "Bitte eine grosse Zahl eingeben: ";
cin >> firstNumber;
cout << "Bitte eine kleinere Zahl eingeben: ";
cin >> secondNumber;
if (firstNumber > secondNumber)
cout << "\nDanke!\n";
else
cout << "\nDie zweite Zahl ist groesser!\n";
return 0;
}
Ausgabe:
bergmann@MB-Workstation:~/Projekte/Cpp-Kurs$ ./ifelse
Bitte eine grosse Zahl eingeben: 10
Bitte eine kleinere Zahl eingeben: 12
Die zweite Zahl ist groesser!
Analyse:
Liefert die Bedingung der if
-Anweisung in Zeile 17 das Ergebnis true
, wird die Anweisung in Zeile 18 ausgeführt. Ergibt sich der Wert false
, führt das Programm die Anweisung in Zeile 20 aus. Wenn man die else
-Klausel in Zeile 19 entfernt, wird die Anweisung in Zeile 20 unabhängig davon ausgeführt, ob die if
-Anweisung true
ist oder nicht. Denke daran, dass die if
-Anweisung nach Zeile 18 endet. Fehlt die else
-Klausel, wäre Zeile 20 einfach die nächste Zeile im Programm.
Statt der einzelnen Anweisungen könntest du auch in geschweifte Klammern eingeschlossen Codeblöcke aufsetzen:
// Listing: ifelseblocks.cpp
// zeigt die if-Anweisung mit
// der else-Klausel, Anweisungen in Codeblocks
#include <iostream>
using namespace std;
int main(void)
{
int firstNumber, secondNumber;
cout << "Bitte eine grosse Zahl eingeben: ";
cin >> firstNumber;
cout << "\nBitte eine kleinere Zahl eingeben: ";
cin >> secondNumber;
if (firstNumber > secondNumber)
{
cout << "\nDanke!\n";
}
else
{
cout << "\nDie zweite Zahl ist groesser!\n";
}
return 0;
}
Beachte die neu hinzugekommenen geschweiften öffnenden und schließenden Klammern in den Zeilen 18, 20, 22 und 24: Jeweils eins dieser Paare bildet einen Codeblock. Tatsächlich musst du sogar Codeblocks verwenden, falls auf eine if-Abfrage mehrere Anweisungen ausgeführt werden sollen.
Syntax: if-Anweisung
Die Syntax einer if
-Anweisung sieht wie folgt aus:
Format 1:
if (Ausdruck)
Anweisung;
nächste_Anweisung;
Liefert die Auswertung von Ausdruck
das Ergebnis true
, wird die Anweisung
ausgeführt und das Programm setzt mit nächste_Anweisung
fort. Ergibt der Ausdruck nicht true
, wird die Anweisung
ignoriert, und das Programm springt sofort zu nächste_Anweisung
.
Anweisung
steht hierbei für eine einzelne, durch Semikolon abgeschlossene Anweisung oder eine in geschweifte Klammern (Codeblock) eingeschlossene Verbundanweisung.
if (Ausdruck)
Anweisung1;
else
Anweisung2;
nächste_Anweisung;
Ergibt der Ausdruck true
, wird Anweisung1
ausgeführt, wenn nicht, kommt die Anweisung2
zur Ausführung. Anschließend fährt das Programm mit nächste_Anweisung
fort.
Beispiel 1
if (EinWert < 10)
cout << "EinWert ist kleiner als 10");
else
cout << "EinWert ist nicht kleiner als 10!");
cout << "Fertig." << endl;
Erweiterte if-Anweisungen
In einer if
– oder else
-Klausel kann jede beliebige Anweisung stehen, sogar eine andere if
– oder else
-Anweisung. Dadurch lassen sich komplexe if
-Anweisungen der folgenden Form erstellen:
if (Ausdruck1)
{
if (Ausdruck2)
Anweisung1;
else
{
if (Ausdruck3)
Anweisung2;
else
Anweisung3;
}
}
else
Anweisung4;
- Diese umständliche
if
-Anweisung sagt aus: »Wenn Ausdruck1
gleich true
ist und Ausdruck2
gleich true
ist, führe Anweisung1
aus. - Wenn
Ausdruck1
gleich true
, aber Ausdruck2
nicht true
ist, dann führe Anweisung2
aus, wenn Ausdruck3
true
ist. - Wenn
Ausdruck1
gleich true
, aber Ausdruck2
und Ausdruck3
gleich false
sind, führe Anweisung3
aus. - Wenn schließlich
Ausdruck1
nicht true
ist, führe Anweisung4
aus.«
Wie man sieht, können komplexe if
-Anweisungen einiges zur Verwirrung beitragen!
[Inhaltsverzeichnis] | [zurück] | [vorwärts]