C++ für einsteiger - ein Basis-Tutorial www.mbergmann-sh.de

C++ Tutorial – Ausdrücke und Anweisungen

„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 meinAlterum 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:

 

 


[Inhaltsverzeichnis] | [zurück] | [vorwärts]