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:

  • 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]

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

C++ Tutorial – Funktionen

Schnelleinstieg: Funktionen in C/C++

C++ Funktionen sind kleine Unterprogramme, die Teilprobleme lösen. Funktionen sind ein wichtiges Werkzeug, um den Quelltext eines Programms zu ordnen und wesentliche Algorithmen und zusammenhängende Anweisungsblöcke der main()-Hauptfunktion in einer zusammenhängenden Form auszulagern. Sie werden außerhalb der main()-Hauptfunktion definiert und vereinfachen somit das Verständnis und die Lesbarkeit des Quelltextes.

Deklaration und Definition von Funktionen

Im Grunde bedeutet die Definition einer Funktion nichts Anderes, als eine Code-Block (Anweisungsblock) mit einem Funktionsnamen zu verbinden. Die Definition einer C++ Funktion besteht aus einer Deklaration und einem Anweisungsblock.

Für die Deklaration einer C++ Funktion sind gewisse Angaben erforderlich und gewisse zusätzliche Spezifikationen möglich (z.B. ‚inline‚ oder ‚virtual‚). Um eine Funktion schließlich zu definieren, muss man zusätzlich noch die zu erledigenden Anweisungen in einem Anweisungsblock zusammenfassen. Die allgemeine Definition einer C++ Funktion ist der formalen Definition einer mathematischen Funktion nicht unähnlich und man könnte sie auch wie folgt definieren: „Eine C++ Funktion ist eine Abbildung von dem Datenraum der Argumentenliste in den Datenraum des Rückgabetyps. Die dabei benutzte Abbildungsvorschrift findet sich in ihrem Anweisungsblock. Formal besitzt sie somit die folgende Struktur:“

'Rückgabe TypFunktionsname ('Argumentenliste') { 'Block von Anweisungen' }

  • Der Name der Funktion:
    Hier sollte der Programmierer einen Namen wählen, der die im Anweisungsblock definierten Anweisungen präzise und kurz in einem Wort zusammen fasst.
  • Rückgabe Typ:
    Welchen Datentyp gibt die Funktion an das Hauptprogramm zurück? Der „Rückgabe Typ“ steht vor dem Funktionsnamen.
  • Argumentenliste:
    Die „Argumentenliste“ steht direkt hinter dem Funktionsnamen, im Aufrufoperator „(…)“. Sie spezifiziert die Datentypen der Variablen, die die Funktion zur Berechnung ihrer Aufgabe benötigt.

Der ‚Rückgabe Typ‘ kann hierbei einer der C++ Datentypen (z.B. int oder double) oder ein Daten-Array  sein. Falls der ‚Rückgabe Typ‘ als void gekennzeichnet wird, gibt die Funktion keine Daten an das Hauptprogramm zurück, und führt nur den ‚Block von Anweisungen‘ aus. Die ‚Argumentenliste‘ setzt sich aus einer Liste von Datentypen der formalen Argumente (Parameter) der Funktion zusammen, die jeweils mit einem Komma voneinander getrennt sind.

Soviel zur Theorie – werden wir praktisch:

Funktionen erfüllen also den Zweck, wiederkehrende Aufgaben auszulagern, anstatt jedes Mal den gleichen Code nochmal einzutippen, wenn eine bestimmte Aufgabe ansteht.

Programme werden Zeile für Zeile ausgeführt, in der Reihenfolge, in der du den Quellcode aufgesetzt hast. Bei einem Funktionsaufruf verzweigt das Programm, um die Funktion auszuführen. Ist die Funktion beendet, springt die Programmausführung zurück zu der Zeile in der aufrufenden Funktion, die auf den Funktionsaufruf folgt. Funktionen dürfen andere Funktionen – auch sich selbst (Rekursion) – aufrufen.

Der Funktion main() kommt dabei eine Sonderstellung zu. Normalerweise muss eine Funktion, um etwas leisten zu können, im Verlauf Ihres Programms durch main() oder eine andere Funktion aufgerufen werden. main() ist die Hauptfunktion eines jeden Programms und wird beim Start des Programms vom Betriebssystem aufgerufen. Sie steuert den eigentlichen Ablauf des Programms. Die Hauptfunktion darf sich nicht selbst aufrufen.

Im realen Leben könnte das so aussehen: Stellen wir uns vor, dass du ein Bild von dir selbst zeichnest. Du zeichnest deinen Kopf, die Augen, die Nase – und plötzlich bricht dein Bleistift ab. Du »verzweigst« nun in die Funktion »Bleistift spitzen«. Das heißt, du hörst mit dem Zeichnen auf, stehst auf, gehst zur Spitzmaschine, spitzt den Stift, kehrst an deine Arbeit zurück und setzt sie dort fort, wo du aufgehört hast. Wenn ein Programm eine bestimmte Arbeit verrichtet haben möchte, kann es dafür eine Funktion aufrufen und nach Abarbeitung der Funktion genau an dem Punkt weitermachen, wo es aufgehört hat. Das Listing „demofunction.cpp“ verdeutlicht dieses Konzept:

// Listing: demofunction.cpp
#include <iostream>

// Namensraum für cout:
using namespace std;
// Funktion DemoFunction()
// - gibt eine Meldung aus und gibt danach
// - die Kontrolle zurück an den Aufrufer:   
void DemoFunction(void)  
{     
  cout << "In DemoFunction\n";
}

// Funktion main - gibt eine Meldung aus, ruft
// dann DemoFunction auf, gibt danach
// eine zweite Meldung aus:
int main()
{ 
  cout << "In main\n" ;
  DemoFunction();   // Verzweigung zur Funktion 'DemoFunction()'

  // Wieder in main() nach Abarbeitung der Funktion:    
  cout << "Zurueck in main\n";
  
  return 0;
}

Hinweis: Bei der Funktion DemoFunction() handelt es sich um eine Funktion ohne Rückgabewert (Datentyp für die Rückgabe: void).

Ausgabe:

./demofunction
In main
In DemoFunction
Zurueck in main

Programmanalyse:

  • Zeile 2 inkludiert IOSTREAM. Das ist eine Header-Datei, die Teil der Standardbibliothek von C++ ist. Diese Datei enthält Definitionen für die Ein- und Ausgabestreams, wie cin, cout, cerr und clog. Diese Streams sind Instanzen von Klassen wie istream und ostream, die wiederum von der Basisklasse ios abgeleitet sind.
  • Zeile 5 legt den Namensraum std fest und erspart dir damit Tipparbeit (vgl. std::cout).
  • Zeile 9 ist der Funktionskopf der Funktion DemoFunction().
  • Die Zeilen 10 und 12 beginnen und beenden den Anweisungsblock (Funktionsrumpf) der Funktion.
  • Zeile 11 sorgt für die Ausgabe der Meldung ‚In DemoFunktion‘, wenn die Funktion aufgerufen wird. Danach wird die Funktion ohne Rückgabewert beendet und die Programmkontrolle wieder an den Aufrufer (in diesem Fall: Die main()-Funktion) zurück gegeben.
  • Die Hauptfunktion main() beginnt in Zeile 17 (Funktionskopf). Ihr Anweisungsblock beginnt in Zeile 18 und endet in Zeile 26.
  • Zeile 19 gibt die Meldung ‚In main‘ und einen Zeilenvorschub aus.
  • Zeile 20 ruft die Funktion ‚DemoFunktion()‘ ohne Parameter auf und übergibt ihr die Programmkontrolle, bis die Funktion abgearbeitet ist.
  • Zeile 23 gibt die Meldung ‚Zurueck in main‘, gefolgt von einem Zeilenvorschub aus. Zu diesem Zeitpunkt liegt die Programmkontrolle bereits wieder bei main().
  • Zeile 25 gibt den Integer-Wert ‚0‘ (Null) an den Aufrufer (Das ist das Betriebssystem, bzw. die Konsole, in welcher das Programm gestartet wurde) zurück. Das Programm ist damit beendet.

Funktionen sind Arbeitstiere. Um ihren Aufgaben gerecht zu werden, können sie Daten (z. B. das Ergebnis einer Berechnung) über ihren Rückgabetyp an den Aufrufer zurückgeben. Um flexible Ergebnisse liefern zu können, verfügt eine Funktion über eine Parameterliste, mit der ihr Werte übergeben werden können. Soll eine Funktion zum Beispiel zwei Zahlen addieren, stellen die Zahlen die Parameter für die Funktion dar. Ein typischer Funktionskopf sieht folgendermaßen aus:

int summe (int a, int b)

Das Listing „addition.cpp“ zeigt eine Funktion, die zwei ganzzahlige Parameter übernimmt und einen ganzzahligen Wert zurückgibt. Kümmere dich momentan nicht um die Syntax oder die Einzelheiten, wie man mit Integer-Werten (beispielsweise int x) arbeitet. Wir kommen darauf zurück.

// Listing: addition.cpp
#include <iostream>

using namespace std;

// Funktion: int Add(int x, int y)
// - addiert zwei an die Funktion übergebene
// - Zahlen und gibt das Ergebnis an den
// - Aufrufer zurück:
int Add(int x, int y)
{
  cout << "\nFunktion 'Add()', erhalten: " << x << ", " << y << "\n";
  return(x + y);   // Addieren und zurückgeben
}

// --- Haupt-Funktion --
int main(void)
{
  int summand1, summand2, summe;

  cout << "Zahlen addieren:\n";
  cout << "================\n";
  cout << "Ich bin in 'main()'!\n";
  cout << "Gib die erste Zahl ein: ";
  cin >> summand1;
  cout << "Gib die zweite Zahl ein: ";
  cin >> summand2;

  // Berechnung:
  // Der Variablen 'summe' wird der Rückgabewert
  // der Funktion 'Add()' zugewiesen:
  summe = Add(summand1, summand2); // Funktionsaufruf und Wertzuweisung

  // Ausgabe des Ergebnisses:
  cout << "\nZurueck in 'main()'!\n";
  cout << summand1 << " + " << summand2 << " = " << summe << endl;
  cout << "\nProgramm beendet.\n";

  return 0;
}

Hinweis: Bei der Funktion ‚int Add(int x, int y)‘ handelt es sich um eine Funktion mit Rückgabewert und Parameterliste.

Compiliere das Programm (<F5> in Geany oder Konsole):

  g++ -Wall -s addition.cpp -o addition

Starte es anschließend und gib nach Aufforderung zwei Ganzzahlen ein (z. B. 4700 und 11).

Ausgabe:

./addition
Zahlen addieren:
================
Ich bin in 'main()'!
Gib die erste Zahl ein: 4700
Gib die zweite Zahl ein: 11

Funktion 'Add()', erhalten: 4700, 11

Zurueck in 'main()'!
4700 + 11 = 4711

Programm beendet.

Programmanalyse:

  • Die Zeilen 10 bis 14 definieren die Funktion ‚int Add(int x, int y)‘. Beachte: Dies geschieht vor der Definition von ‚main()‘!
  • Zeile 12 sorgt dafür, dass beim Aufruf von Add() eine Meldung ausgegeben wird, die auch die beiden übergebenen Parameter ausgibt.
  • Die Hauptfunktion main() erstreckt sich über die Zeilen 17 bis 40.
  • In Zeile 19 deklarieren wir drei Variablen vom Typ Ganzzahl (int). Sie dienen später der Aufnahme der beiden Summanden und der berechneten Summe.
  • Zeile 24 fordert zur Eingabe einer Zahl auf.
  • Zeile 25 liest diese Zahl mit dem Eingabestream cin ein. Dieses Schlüsselwort steht für „Console Input“ und wird vom Eingabeoperator „>>“ (dem Gegenstück zum Ausgabeoperator „<<„) gefolgt. Er verweist auf die Variable summand1, die die eingegebenen Daten (die erste Ganzzahl) aufnehmen soll.
  • In den Zeilen 26 und 27 wiederholt sich dieser Vorgang der Dateneingabe für die Variable summand2.
  • Zeile 32 – Hier wird die eigentliche Arbeit geleistet: Der Variablen summe wird das Ergebnis der Berechnung, also der Rückgabewert der Funktion Add() zugewiesen. Hierzu ruft die Zuweisung die Funktion Add() mit den Parametern (summand1, summand2) auf. Überraschung! Hatten wir nicht die Parameterlist von Add() als (int x, int y) definert? Doch, hatten wir – und das ist auch korrekt. Die Parameter x und y sind lokale, nur innerhalb der Funktion selbst sichtbare Variablen. Der Aufruf von außerhalb kann mit jeder passenden Variablen, gleich welchen Namens, erfolgen. Intern arbeitet die Funktion allerdings mit den Variablen x, y. Das kann man gut an den Zeilen 12 und 13 erkennen.
  • Zeile 36 gibt beide eingegebenen Zahlen und ihre Summe in der Form aus, wie du die Berechnung auch auf ein Blatt Papier schreiben würdest:
    4700 + 11 = 4711
  • Zeile 39 beendet das Hauptprogramm mit dem Rückgabewert Null.

Funktionsprototypen

Bisher haben wir eigene Funktionen immer vor der Hauptfunktion main() definiert. Das ist dem Umstand geschuldet, dass in C/C++ eine Variable oder Funktion immer vor ihrer ersten Verwendung deklariert sein muss. Es geht aber auch anders, wenn man dem Compiler eine Funktion durch ihren Funktionsprototypen rechtzeitig – also vor main() – bekannt macht. Dabei entspricht der Funktionsprototyp genau dem Funktionskopf, gefolgt von einem Semikolon:

// Listing: addition2.cpp
#include <iostream>

using namespace std;

// Funktionsprototyp:
int Add(int x, int y);

// -- Hauptprogramm --
int main(void)
{
  int summand1, summand2;

  cout << "Zahlen addieren:\n";
  cout << "================\n";
  cout << "Ich bin in 'main()'!\n";
  cout << "Gib die erste Zahl ein: ";
  cin >> summand1;
  cout << "Gib die zweite Zahl ein: ";
  cin >> summand2;

  // Berechnung und Ausgabe:
  cout << summand1 << " + " << summand2 << " = " << Add(summand1, summand2) << endl; 
  return 0;
}

// -- Funktion: int Add(int x, int y) --
int Add(int x, int y)
{
  return(x + y);   // Addieren und zurückgeben
}

Beachte: Im Listing „addition2.cpp“ erfolgt die Definition der Funktion „Add()“ erst nach der Hauptfunktion main(). Um die Funktion trotzdem bereitstellen zu können, wurde in Zeile 7 der Funktionsprototyp entsprechend dem Funktionskopf von Add() deklariert. Auch erfolgt die Berechnung nicht mehr mit Zwischenspeicherung des Ergebnisses in der Variablen summe – diese wurde eingespart. Stattdessen übergeben wir in Zeile 23 den Rückgabewert von Add() direkt an den Ausgabestream.

Die Verwendung von Prototypen hat übrigens den Vorteil, dass der Compiler die korrekte Verwendung einer Funktion bereits frühzeitig überprüfen und auf etwaige Regelverstöße genauer reagieren kann. Gute C/C++-Programme beinhalten deshalb einen Abschnitt, in dem alle Prototypen für Funktionen vor ihrer ersten Verwendung deklariert werden.

Zusammenfassung:

Funktionen geben entweder einen Wert oder void (das heißt: nichts) zurück. Eine Funktion zur Addition von zwei ganzen Zahlen liefert sicherlich die Summe zurück, und man definiert diesen Rückgabewert vom Typ Integer (int). Eine Funktion, die lediglich eine Meldung ausgibt, hat nichts zurückzugeben und wird daher als void (zu deutsch: leer) deklariert.

Funktionen gliedern sich in Kopf und Rumpf. Der Kopf besteht wiederum aus dem Rückgabetyp, dem Funktionsnamen und den Parametern. Mit Parametern lassen sich Werte an eine Funktion übergeben.

Funktionen müssen (wie Variablen) vor ihrer ersten Verwendung dem Compiler bekannt gemacht werden. Aus diesem Grund definiert man sie entweder vor main(), oder aber man verwendet stattdessen einen entsprechenden Funktionsprototypen an ihrer Stelle.


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