vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 7


Mehr zur Programmsteuerung

Programme verbringen den größten Teil ihrer Arbeit mit Verzweigungen und Schleifen. In Kapitel 4, »Ausdrücke und Anweisungen«, haben Sie gelernt, wie Sie mit der if-Anweisung Ihr Programm verzweigen können. Heute lernen Sie,

Schleifenkonstruktionen

Viele Probleme der Programmierung lassen sich durch wiederholtes Ausführen einer oder mehrerer Anweisungen lösen. Dazu bieten sich zwei Möglichkeiten an. Zum einen die Rekursion (die bereits in Kapitel 5, »Funktionen«, behandelt wurde) und zum anderen die Iteration. Iteration bedeutet die wiederholte Ausführung einer Aktion und wird üblicherweise in Form von Schleifen implementiert.

Ursprung der Schleifen - Konstruktionen mit goto

In den frühen Tagen der Informatik waren Programme primitiv, unverständlich und kurz. Schleifen bestanden aus einer Sprungmarke (Label), einigen Anweisungen und einem Sprung.

In C++ ist ein Label einfach ein Name, gefolgt von einem Doppelpunkt. Das Label steht links von einer zulässigen C++-Anweisung. Einen Sprung realisiert man mit der Anweisung goto und dem sich anschließenden Namen des Labels. Listing 7.1 zeigt dazu ein Beispiel.

Listing 7.1: Schleifenkonstruktion mit goto

1:    // Listing 8.1
2: // Schleifen mit goto
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // Zähler initialisieren
9: loop: counter ++; // Anfang der Schleife
10: cout << "Zaehler: " << counter << "\n";
11: if (counter < 5) // Den Wert testen
12: goto loop; // Sprung an Anfang der Schleife
13:
14: cout << "Fertig. Zaehler: " << counter << ".\n";
15: return 0;
16: }

Zähler: 1
Zähler: 2
Zähler: 3
Zähler: 4
Zähler: 5
Fertig. Zaehler: 5.

Zeile 8 initialisiert den Zähler (counter) mit 0. Das Label loop in Zeile 9 markiert den Beginn der Schleife. Das Programm inkrementiert den Wert von counter und gibt den neuen Wert aus. Zeile 11 testet den Wert von counter. Ist er kleiner als 5, liefert die if-Anweisung das Ergebnis true. Das Programm führt daraufhin die goto-Anweisung aus und springt zurück zu Zeile 9. Der Schleifendurchlauf wiederholt sich, bis counter gleich 5 ist. Das Programm übergeht dann die Schleife und führt die letzte Ausgabeanweisung (Zeile 14) aus.

Warum man goto nicht verwenden sollte

goto-Konstruktionen sind schon früh ins Kreuzfeuer der Kritik geraten, und das nicht einmal zu unrecht. Mit goto-Anweisungen läßt sich ein Sprung zu einer beliebigen Stelle im Quellcode realisieren, rückwärts oder vorwärts. Die unüberlegte Verwendung von goto-Anweisungen führt zu unübersichtlichen, schlechten und schwer zu lesenden Programmen, die man als Spaghetti-Code bezeichnet. Deshalb hämmern Informatik- Dozenten ihren Studenten seit über zwanzig Jahren ein, goto möglichst nicht zu verwenden.

Der strukturierten Programmierung kommen die intelligenteren Schleifenbefehle for, while und do...while entgegen, mit denen man goto-Konstruktionen von vornherein vermeiden kann. Man kann jedoch zu Recht argumentieren, daß mal wieder alles etwas übertrieben wird. Denn wie jedes Werkzeug kann goto, sorgfältig eingesetzt und in den richtigen Händen, eine nützliche Konstruktion sein, und das ANSI-Komitee hat entschieden, goto weiter in der Sprache zu behalten, da es seine berechtigten Einsatzbereiche hat. Aber wie sagt man so schön »Kinder, macht das nicht zu Hause nach.«

Die goto-Anweisung

Die Syntax der goto-Anweisung besteht aus dem goto-Befehl und einem Labelnamen. Damit springen Sie, ohne weitere Bedingungen zu berücksichtigen, zu dem Label.

Beispiel:

if (wert > 10) goto end;

if (wert < 10) goto end;

cout << "wert ist gleich 10!";

end: cout << "fertig";

goto zu verwenden ist fast immer ein Zeichen für einen schlechten Entwurf. Am besten versuchen Sie, goto zu vermeiden. In meinen 10 Jahren als Programmierer habe ich es erst einmal verwendet.

while-Schleifen

Eine while-Schleife bewirkt die Wiederholung einer Folge von Anweisungen im Programm, solange die Startbedingung gleich true bleibt. Das goto-Beispiel in Listing 7.1 inkrementiert den Zähler (counter), bis er den Wert 5 erreicht. Listing 7.2 zeigt das gleiche Programm, das jetzt mit der while-Konstruktion realisiert ist.

Listing 7.2: while-Schleifen

1:    // Listing 7.2
2: // Schleifen mit while
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // Bedingung initialisieren
9:
10: while(counter < 5) // Testbedingung immer noch true
11: {
12: counter++; // Rumpf der Schleife
13: cout << "Zähler: " << counter << "\n";
14: }
15:
16: cout << "Fertig. Zaehler: " << counter << ".\n";
17: return 0;
18: }

Zähler: 1
Zähler: 2
Zähler: 3
Zähler: 4
Zähler: 5
Fertig. Zaehler: 5.

Dieses einfache Beispiel demonstriert die Grundlagen der while-Schleife. Liefert der Test einer Bedingung den Wert true, wird der Rumpf der while-Schleife ausgeführt. Im Beispiel testet die Bedingung in Zeile 10, ob counter kleiner als 5 ist. Ergibt der Test true, führt das Programm den Rumpf der Schleife aus: Zeile 12 inkrementiert den Zähler, und Zeile 13 gibt den Wert aus. Wenn die Bedingungsanweisung in Zeile 10 den Wert false ergibt (wenn counter nicht mehr kleiner als 5 ist), wird der gesamte Rumpf der while-Schleife (in den Zeilen 11 bis 14) übersprungen. Die Programmausführung setzt dann sofort mit Zeile 15 fort.

Die while-Anweisung

Die Syntax der while-Anweisung lautet:

     while ( bedingung )
anweisung;

bedingung ist ein beliebiger C++-Ausdruck und anweisung eine beliebige C++-Anweisung oder ein Block von Anweisungen. Ergibt die bedingung true (1), wird anweisung ausgeführt und danach bedingung erneut getestet. Dieser Vorgang wiederholt sich so lange, bis der Test von bedingung false ergibt. Daraufhin wird die while-Schleife verlassen, und die Ausführung setzt mit der ersten Zeile unter anweisung fort.

Beispiel:

     //  bis 10 zählen
int x = 0;
while (x < 10)
cout << "X: " << x++;

Komplexere while-Anweisungen

Die in einer while-Schleife getestete Bedingung kann aus jedem zulässigen C++-Ausdruck bestehen. Es lassen sich auch Ausdrücke einbinden, die man mit den logischen Operatoren && (AND), || (OR) und ! (NOT) erstellt. Listing 7.3 zeigt eine etwas kompliziertere while-Anweisung.

Listing 7.3: Komplexe while-Schleifen

1:    // Listing 7.3
2: // Komplexe while-Anweisungen
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short small;
9: unsigned long large;
10: const unsigned short MAXSMALL=65535;
11:
12: cout << "Bitte eine kleine Zahl eingeben: ";
13: cin >> small;
14: cout << "Bitte eine grosse Zahl eingeben: ";
15: cin >> large;
16:
17: cout << "Klein: " << small << "...";
18:
19: // Bei jedem Schleifendurchlauf drei Bedingungen testen
20: while (small < large && large > 0 && small < MAXSMALL)
21:
22: {
23: if (small % 5000 == 0) // Alle 5000 Zeilen einen Punkt ausgeben
24: cout << ".";
25:
26: small++;
27:
28: large-=2;
29: }
30:
31: cout << "\nKlein: " << small << " Gross: " << large << endl;
32: return 0;
33: }

Bitte eine kleine Zahl eingeben: 2
Bitte eine grosse Zahl eingeben: 100000
Klein: 2.........
Klein: 33335 Gross: 33334

Dieses Programm stellt ein Spiel dar. Man gibt zwei Zahlen ein, eine kleine (small) und eine große (large). Die kleinere Zahl wird um 1 nach oben gezählt, die größere in Schritten von 2 abwärts. Es ist nun zu erraten, wann sich die Zahlen treffen.

In den Zeilen 12 bis 15 erfolgt die Eingabe der Zahlen. Zeile 20 richtet eine while- Schleife ein, deren Durchläufe von drei Bedingungen abhängig sind:

Zeile 23 berechnet den Wert von small modulo 5.000. Der Wert in small bleibt dabei unverändert. Der Ausdruck liefert das Ergebnis 0, wenn small ein genaues Vielfaches von 5.000 ist. In diesem Fall gibt das Programm als Fortschrittskontrolle einen Punkt auf dem Bildschirm aus. Zeile 26 inkrementiert den Wert von small, während Zeile 28 den Wert von large um 2 dekrementiert.

Wenn irgendeine der drei Bedingungen in der while-Schleife nicht erfüllt ist, endet die Schleife, und die Ausführung des Programms setzt sich nach der schließenden Klammer der while-Schleife in Zeile 29 fort.

Der Modulo-Operator (%) und komplexe Bedingungen wurden am Tag 3, »Variablen und Konstanten«, besprochen.

continue und break

Manchmal soll das Programm an den Anfang einer while-Schleife zurückkehren, bevor die gesamte Gruppe von Anweisungen in der while-Schleife abgearbeitet ist. Die continue-Anweisung bewirkt einen Sprung zurück an den Beginn der Schleife.

Es kann auch sein, daß man die Schleife verlassen muß, bevor die Abbruchbedingung erfüllt ist. Die break-Anweisung führt unmittelbar zum Austritt aus der while-Schleife, und die Programmausführung wird nach der schließenden geschweiften Klammer fortgesetzt.

Listing 7.4 demonstriert die Verwendung dieser Anweisungen. Dieses Mal ist das Spiel etwas komplizierter. Der Anwender wird aufgefordert, eine kleine Zahl (small), eine große Zahl (large), eine Sprungzahl (skip) und eine Zielzahl (target) einzugeben. Das Programm inkrementiert die Zahl small um 1 und dekrementiert die Zahl large um 2. Das Dekrementieren wird übersprungen, wenn small ein Vielfaches der Zahl skip ist. Das Spiel endet, sobald small größer als large ist. Wenn die Zahl large genau die Zielzahl target trifft, erscheint eine Mitteilung, und das Spiel stoppt.

Der Anwender muß versuchen, eine Zielzahl für die Zahl large einzugeben, die das Spiel stoppt.

Listing 7.4: Die Anweisungen break und continue

1:    // Listing 7.4
2: // Demonstriert die Anweisungen break und continue
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short small;
9: unsigned long large;
10: unsigned long skip;
11: unsigned long target;
12: const unsigned short MAXSMALL=65535;
13:
14: cout << "Bitte eine kleine Zahl eingeben: ";
15: cin >> small;
16: cout << "Bitte eine grosse Zahl eingeben: ";
17: cin >> large;
18: cout << "Bitte eine Sprungzahl eingeben: ";
19: cin >> skip;
20: cout << "Bitte eine Zielzahl eingeben: ";
21: cin >> target;
22:
23: cout << "\n";
24:
25: // 3 Abbruchbedingungen für die Schleife einrichten
26: while (small < large && large > 0 && small < MAXSMALL)
27:
28: {
29:
30: small++;
31:
32: if (small % skip == 0) // Dekrementieren überspringen?
33: {
34: cout << "Ueberspringen " << small << endl;
35: continue;
36: }
37:
38: if (large == target) // target genau getroffen?
39: {
40: cout << "Ziel getroffen!";
41: break;
42: }
43:
44: large-=2;
45: } // Ende der while-Schleife
46:
47: cout << "\nKlein: " << small << " Gross: " << large << endl;
48: return 0;
48: }

Bitte eine kleine Zahl eingeben: 2
Bitte eine grosse Zahl eingeben: 20
Bitte eine Sprungzahl eingeben: 4
Bitte eine Zielzahl eingeben: 6
Ueberspringen 4
Ueberspringen 8
Klein: 10 Gross: 8

In diesem Spiel hat der Anwender verloren. small wurde größer als large, bevor er die target-Zahl von 6 erreicht hat.

In Zeile 26 steht der Test der while-Bedingungen. Wenn small weiterhin kleiner als large ist, large größer als 0 ist und small noch nicht den Maximalwert für eine kleine int-Zahl überschritten hat, tritt die Programmausführung in den Rumpf der Schleife ein.

Die Anweisung in Zeile 32 berechnet den Rest der Ganzzahldivision von small und skip. Wenn small ein Vielfaches von skip ist, wird die continue-Anweisung erreicht, und die Programmausführung springt an den Beginn der Schleife in Zeile 26. Damit übergeht das Programm den Test auf target und das Dekrementieren von large.

Zeile 38 testet target erneut gegen den Wert für large. Sind beide Werte gleich, hat der Anwender gewonnen. Es erscheint eine Meldung, und das Programm erreicht die break-Anweisung. Das bewirkt einen sofortigen Austritt aus der while-Schleife, und die Programmausführung wird mit Zeile 46 fortgesetzt.

Sowohl continue als auch break sollte man mit Umsicht einsetzen. Nach goto sind es die beiden gefährlichsten Befehle, und zwar aus den gleichen Gründen wie bei goto angeführt. Programme, die plötzlich die Richtung ändern, sind schwerer zu verstehen, und der großzügige Einsatz von continue und break macht sogar eine kleine while-Schleifenkonstruktion unverständlich.

Die continue-Anweisung

Die Anweisung continue; bewirkt, daß eine while- oder for-Schleife wieder zum Anfang der Schleife zurückkehrt. In Listing 7.4 finden Sie ein Beispiel für den Einsatz von continue.

Die break-Anweisung

Die break-Anweisung bewirkt den direkten Ausstieg aus eine while- oder for-Schleife. Die Programmausführung springt zu der schließenden geschweiften Klammer.

Beispiel:

while (bedingung)
{
if (bedingung2)
break;
// anweisungen;
}

while(true)-Schleifen

Bei der in einer while-Schleife getesteten Bedingung kann es sich um einen beliebigen gültigen C++-Ausdruck handeln. Solange die Bedingung true bleibt, wird die while- Schleife fortgesetzt. Man kann eine Endlosschleife erzeugen, indem man für die zu testende Bedingung den Wert true angibt. Listing 7.5 realisiert mit Hilfe dieser Konstruktion einen Zähler bis 10.

Listing 7.5: while(true)-Schleifen

1:    // Listing 7.5
2: // Demonstriert eine while-true-Schleife
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0;
9:
10: while (true)
11: {
12: counter ++;
13: if (counter > 10)
14: break;
15: }
16: cout << "Zaehler: " << counter << "\n";
17: return 0;
18: }

Zähler: 11

Zeile 10 richtet eine while-Schleife mit einer Bedingung ein, die niemals false liefern kann. Die Schleife inkrementiert in Zeile 12 die Zählervariable (counter) und testet dann in Zeile 13, ob counter den Wert 10 überschritten hat. Ist das nicht der Fall, führt die Schleife einen erneuten Durchlauf aus. Wenn counter größer als 10 ist, beendet die break-Anweisung in Zeile 14 die while-Schleife, und die Programmausführung geht direkt zu Zeile 16, wo die Ausgabe der Ergebnisse stattfindet.

Dieses Programm funktioniert zwar, ist aber nicht sehr elegant formuliert - ein gutes Beispiel für das falsche Werkzeug. Das gleiche kann man realisieren, wenn man den Test des Zählerwertes dorthin schreibt, wo er hingehört: in die while-Bedingung.

Endlosschleifen wie while(true) können dazu führen, daß sich der Computer aufhängt, wenn die Abbruchbedingung niemals erreicht wird. Verwenden Sie diese Konstruktion mit Vorsicht, und führen Sie gründliche Tests durch.

Was Sie tun sollten

... und was nicht

Verwenden Sie while-Schleifen, um einen Anweisungsblock so lange zu wiederholen, wie eine entsprechende Bedingung true ist.

Setzen Sie die Anweisungen continue und break mit Umsicht ein.

Stellen Sie sicher, daß es einen Austritt aus der Schleife gibt.

Verzichten Sie auf die goto-Anweisung.

C++ bietet verschiedene Möglichkeiten, um die gleiche Aufgabe zu realisieren. Das eigentliche Kunststück ist es, das richtige Werkzeug für die jeweilige Aufgabe herauszusuchen.

do...while-Schleifen

Es kann sein, daß der Rumpf einer while-Schleife gar nicht zur Ausführung gelangt. Die while-Anweisung prüft die Bedingung vor allen Anweisungen. Liefert die Bedingung false, überspringt das Programm den gesamten Rumpf der Schleife. Listing 7.6 verdeutlicht das.

Listing 7.6: Den Rumpf der while-Schleife überspringen

1:      // Listing 7.6
2: // Den Rumpf der while-Schleife überspringen, wenn
3: // die Bedingung false ist.
4:
5: #include <iostream.h>
6:
7: int main()
8: {
9: int counter;
10: cout << "Wie viele Hallos?: ";
11: cin >> counter;
12: while (counter > 0)
13: {
14: cout << "Hallo!\n";
15: counter--;
16: }
17: cout << "Zaehler ist: " << counter;
18: return 0;
19: }

Wie viele Hallos?: 2
Hallo!
Hallo!
Zähler ist: 0

Wie viele Hallos?: 0
Zähler ist Ausgabe: 0

Die Anweisung in Zeile 10 fordert den Anwender zur Eingabe eines Startwerts auf, der in der Integer-Variablen counter gespeichert wird. Der Wert in der Variablen wird in Zeile 12 überprüft und im Rumpf der while-Schleife dekrementiert. Beim ersten Programmdurchlauf hat der Anwender für counter die Zahl 2 eingegeben, so daß die Schleife zweimal ausgeführt wurde. Beim zweiten Mal hat er allerdings eine 0 eingegeben. Beim Test von counter in Zeile 12 liefert die Bedingung false, da counter nicht größer als 0 ist. Damit überspringt das Programm die gesamte while-Schleife, und die Meldung »Hallo!« erscheint überhaupt nicht.

Wie kann man nun sicherstellen, daß »Hallo!« wenigstens einmal zu sehen ist? Die while-Schleife kann das nicht realisieren, da der Test der Bedingung vor jeglicher Ausgabe erfolgt. Man kann die Schleifenausführung erzwingen, indem man eine if-Anweisung unmittelbar vor dem Eintritt in die Schleife einbaut:

if (counter < 1)  // Einen Minimalwert erzwingen
counter = 1;

Das ist aber eine häßliche und wenig elegante Lösung.

do...while

Die do...while-Schleife führt den Rumpf der Schleife aus, bevor der Test der Bedingung stattfindet. Damit ist gesichert, daß der Rumpf mindestens einmal abgearbeitet wird. Listing 7.7 zeigt das umgeschriebene Programm von Listing 7.6 mit einer do...while-Schleife.

Listing 7.7: Demonstration einer do..while-Schleife

1:      // Listing 7.7
2: // Demonstriert eine do while-Schleife
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter;
9: cout << "Wie viele Hallos? ";
10: cin >> counter;
11: do
12: {
13: cout << "Hallo\n";
14: counter--;
15: } while (counter >0 );
16: cout << "Zaehler ist: " << counter << endl;
17: return 0;
18: }

Wie viele Hallos? 2
Hallo
Hallo
Zähler ist: 0

Die Anweisung in Zeile 9 fordert den Anwender zur Eingabe eines Startwerts auf, der in der Integer-Variablen counter gespeichert wird. Das Programm tritt in die do...while-Schleife ein, bevor der Test der Bedingung erfolgt. Damit ist die Ausführung der Schleife mindestens einmal garantiert. In Zeile 13 steht die Ausgabe der Meldung, Zeile 14 dekrementiert den Zähler, und in Zeile 15 findet der Test der Bedingung statt. Wenn die Bedingung den Wert true ergibt, springt die Ausführung an den Anfang der Schleife in Zeile 13, andernfalls geht es direkt zu Zeile 16.

Die Anweisungen continue und break arbeiten in der do...while-Schleife genauso wie in der while-Schleife. Der einzige Unterschied zwischen einer while- und einer do...while-Schleife besteht im Zeitpunkt für den Test der Bedingung.

Die do...while-Anweisung

Die Syntax für die do...while-Anweisung lautet:

     do
anweisung
while (bedingung);\

Erst wird anweisung ausgeführt und dann bedingung getestet. Ergibt bedingung true, wird die Schleife wiederholt, ansonsten endet die Schleife. Die Anweisungen und Bedingungen entsprechen denen der while-Schleife.

Beispiel 1:

     // bis 10 zaehlen
in x = 0;
do
cout << "X: " << x++;
while (x < 10)

Beispiel 2:

     // Alphabet in Kleinbuchstaben ausgeben
char ch = 'a';
do
{
cout << ch << ' ';
ch++;
} while ( ch <= 'z' );

Was Sie tun sollten

Arbeiten Sie mit do...while-Schleifen, wenn Sie sicherstellen wollen, daß die Schleife zumindest einmal durchlaufen wird.

Arbeiten Sie mit while-Schleifen, wenn Sie für den Fall, daß die Bedingung false ist, die Schleife überspringen wollen.

Testen Sie alle Schleifen, um sicherzustellen, daß sie auch wie gewünscht funktionieren.

for-Schleifen

Bei der Programmierung von while-Schleifen stellt man häufig fest, daß man eine Startbedingung festlegt, eine Bedingung auf true testet und eine Variable bei jedem Schleifendurchlauf inkrementiert oder anderweitig verändert. Listing 7.8 zeigt dazu ein Beispiel.

Listing 7.8: Untersuchung einer while-Schleife

1:    // Listing 7.8
2: // Schleifendurchlaeufe mit while
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0;
9:
10: while(counter < 5)
11: {
12: counter++;
13: cout << "Schleife! ";
14: }
15:
16: cout << "\nZaehler: " << counter << ".\n";
17: return 0;
18: }

Schleife!  Schleife!  Schleife!  Schleife!  Schleife!
Zähler: 5.

Zeile 8 initialisiert die Variable counter für die Anfangsbedingung mit 0. Der Test in Zeile 10 prüft, ob counter kleiner als 5 ist. In Zeile 12 wird counter inkrementiert. Zeile 13 gibt nur eine einfache Meldung aus - hier sind aber auch wichtigere Aufgaben denkbar, die bei jedem Inkrementieren von counter zu erledigen sind.

Eine for-Schleife faßt die drei Schritte Initialisierung, Test und Inkrementierung in einer Anweisung zusammen. Die Syntax der for-Anweisung besteht aus dem Schlüsselwort for, gefolgt von einem Klammernpaar. Innerhalb dieser Klammern befinden sich drei durch Semikolons getrennte Anweisungen.

Die erste Anweisung ist die Initialisierung. Hier kann man jede zulässige C++-Anweisung angeben. Normalerweise verwendet man diese Anweisung aber, um eine Zählervariable zu erzeugen und zu initialisieren. Die zweite Anweisung realisiert den Test und kann ebenfalls jeder zulässige C++-Ausdruck sein. Diese Anweisung übernimmt die Rolle der Bedingung in der while-Schleife. Anweisung drei ist die Aktion. Normalerweise inkrementiert oder dekrementiert man einen Wert, obwohl auch alle zulässigen C++-Anweisungen möglich sind. Beachten Sie, daß zwar die erste und dritte Anweisung alle in C++ zulässigen Anweisungen sein können, daß aber für die zweite Anweisung ein Ausdruck erforderlich ist - eine C++-Anweisung, die einen Wert zurückgibt. Listing 7.9 demonstriert die for-Schleife.

Listing 7.9: Beispiel für eine for-Schleife

1:      // Listing 7.9
2: // Schleifendurchlaeufe mit for
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter;
9: for (counter = 0; counter < 5; counter++)
10: cout << "Schleife! ";
11:
12: cout << "\nZaehler: " << counter << ".\n";
13: return 0;
14: }

Schleife!  Schleife!  Schleife!  Schleife!  Schleife!
Zähler: 5.

Die for-Anweisung in Zeile 9 kombiniert die Initialisierung von counter, den Test, ob counter kleiner als 5 ist, und die Inkrementierung von counter in einer Zeile. Der Rumpf der for-Anweisung steht in Zeile 10. Natürlich könnte man hier genausogut einen Block vorsehen.

Die for-Anweisung

Die Syntax für die for-Anweisung lautet:

     for (initialsierung; test; aktion)
anweisung;

Die Initialisierungsanweisung dient dazu, einen Zähler zu initialisieren oder auf andere Art und Weise die for-Schleife einzuleiten. Bei test handelt es sich um einen beliebigen C++-Ausdruck, der bei jedem Schleifendurchlauf geprüft wird. Ergibt test den Wert true, wird zuerst der Rumpf der for-Schleife und anschließend die Aktion im Kopf der for-Schleife (normalerweise Inkrement des Zählers) ausgeführt.

Beispiel 1:

     // Hallo 10mal ausgeben
for (int i = 0; i < 10; i++)
cout << "Hallo! ";

Beispiel 2:

     for (int i = 0; i < 10; i++)
{
cout << "Hallo!" << endl;
cout << "der Wert von i beträgt: " << i << endl;
}

Erweiterte for-Schleifen

for-Anweisungen sind leistungsfähig und flexibel. Die drei unabhängigen Anweisungen (Initialisierung, Test und Aktion) führen von selbst zu einer Reihe von Varianten.

Eine for-Schleife arbeitet nach folgendem Schema:

  1. Ausführen der Operationen in der Initialisierung.
  2. Auswerten der Bedingung.
  3. Wenn die Bedingung true ergibt: Ausführen der Schleife und dann der Aktionsanweisung.

Nach jedem Schleifendurchlauf wiederholt die for-Schleife die Schritte 2 und 3.

Mehrfache Initialisierung und Inkrementierung

Es ist durchaus üblich, mehrere Variablen auf einmal zu initialisieren, einen zusammengesetzten logischen Ausdruck zu testen und mehrere Anweisungen auszuführen. Die Anweisungen für Initialisierung und Aktion lassen sich durch mehrere C++-Anweisungen ersetzen, die jeweils durch Komma zu trennen sind. Listing 7.10 zeigt die Initialisierung und Inkrementierung von zwei Variablen.

Listing 7.10: Mehrere Anweisungen in for-Schleifen

1:  // Listing 7.10
2: // Demonstriert mehrere Anweisungen in
3: // for-Schleifen
4:
5: #include <iostream.h>
6:
7: int main()
8: {
9: for (int i=0, j=0; i<3; i++, j++)
10: cout << "i: " << i << " j: " << j << endl;
11: return 0;
12: }

i: 0  j: 0
i: 1 j: 1
i: 2 j: 2

Zeile 9 initialisiert die beiden Variablen i und j mit dem Wert 0. Die Auswertung der Testbedingung (i<3) liefert true. Somit führt die for-Konstruktion die Anweisungen im Rumpf aus: hier die Ausgabe der Werte. Schließlich wird die dritte Klausel in der for- Anweisung ausgeführt: Inkrementieren von i und j.

Nach Abarbeitung von Zeile 10 wertet die for-Konstruktion die Bedingung erneut aus. Liefert die Auswertung weiterhin true, wiederholt die for-Schleife die Aktionen (Inkrementieren von i und j) und führt den Rumpf der Schleife erneut aus. Das setzt sich so lange fort, bis der Test false ergibt. Die Aktionsanweisung gelangt dann nicht mehr zur Ausführung, und der Programmablauf setzt sich nach der Schleife fort.

Leere Anweisungen in for-Schleifen

In einer for-Schleife können einige oder alle Anweisungen leer sein. Dazu markiert man mit einem Semikolon die Stelle, wo die Anweisung normalerweise steht. Um eine for-Schleife zu erzeugen, die genau wie eine while-Schleife arbeitet, läßt man die ersten und dritten Anweisungen weg. Dazu zeigt Listing 7.11 ein Beispiel.

Listing 7.11: Eine for-Schleife mit leeren Anweisungen

1:    // Listing 7.11
2: // for-Schleife mit leeren Anweisungen
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0;
9:
10: for( ; counter < 5; )
11: {
12: counter++;
13: cout << "Schleife! ";
14: }
15:
16: cout << "\nZaehler: " << counter << ".\n";
17: return 0;
18: }

Schleife!  Schleife!  Schleife!  Schleife!  Schleife!
Zähler: 5.

Ein Vergleich mit der weiter vorn in Listing 7.8 gezeigten while-Schleife läßt eine nahezu identische Konstruktion erkennen. Die Initialisierung der Zählervariablen erfolgt in Zeile 8. Die for-Anweisung in Zeile 10 initialisiert keinerlei Werte, enthält aber einen Test für counter < 5. Weiterhin fehlt eine Inkrement-Anweisung, so daß sich diese Schleife wie die folgende Konstruktion verhält:

while (counter < 5)

Auch hier bietet C++ verschiedene Möglichkeiten, dasselbe zu verwirklichen. Kein erfahrener C++-Programmierer würde eine for-Schleife auf diese Weise verwenden. Das Beispiel verdeutlicht aber die Flexibilität der for-Anweisung. In der Tat ist es mit break und continue möglich, eine for-Schleife mit keiner der drei Anweisungen zu realisieren. Listing 7.12 zeigt dazu ein Beispiel.

Listing 7.12: Eine leere for-Schleifenanweisung

1:     //Listing 7.12 zeigt eine
2: //leere for-Schleifenanweisung
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter=0; // Initialisierung
9: int max;
10: cout << "Wie viele Hallos? ";
11: cin >> max;
12: for (;;) // Endlosschleife mit for
13: {
14: if (counter < max) // Test
15: {
16: cout << "Hallo!\n";
17: counter++; // Inkrementieren
18: }
19: else
20: break;
21: }
22: return 0;
23: }

Wie viele Hallos?  3
Hallo!
Hallo!
Hallo!

Damit hat man die for-Schleife bis zu ihrem absoluten Limit ausgereizt. Initialisierung, Test und Aktion wurden gänzlich aus der for-Anweisung herausgenommen. Die Initialisierung findet man in Zeile 8, bevor die for-Schleife überhaupt beginnt. Der Test erfolgt in einer separaten if-Anweisung in Zeile 14. Verläuft er erfolgreich, wird die Aktion - Inkrementieren von counter - in Zeile 17 ausgeführt. Wenn der Test scheitert, verläßt das Programm die Schleife mit der Anweisung in Zeile 20.

Wenn dieses Programm auch etwas absurd erscheint, kann es durchaus sein, daß man genau eine Schleife im Stil von for(;;) oder while(true) braucht. Ein Beispiel für einen sinnvolleren Einsatz derartiger Schleifen bei switch-Anweisungen folgt weiter hinten in diesem Kapitel.

Leere for-Schleifen

Der Kopf einer for-Anweisung bietet sehr viel Spielraum, so daß man manchmal auf einen Rumpf gänzlich verzichten kann. In diesem Fall muß man eine leere Anweisung (;) als Rumpf der Schleife vorsehen. Das Semikolon darf auf derselben Zeile wie der Kopf stehen, obwohl man es an dieser Stelle leicht übersehen kann. Listing 7.13 zeigt dazu ein Beispiel.

Listing 7.13: Darstellung einer leeren Anweisung als Rumpf einer for-Schleife

1:     // Listing 7.13
2: // Zeigt eine leere Anweisung als Rumpf
3: // einer for-Schleife
4:
5: #include <iostream.h>
6: int main()
7: {
8: for (int i = 0; i<5; cout << "i: " << i++ << endl)
9: ;
10: return 0;
11: }

i: 0
i: 1
i: 2
i: 3
i: 4

Die for-Schleife in Zeile 8 enthält drei Anweisungen: Die Initialisierungsanweisung richtet den Zähler i ein und initialisiert ihn zu 0. Die Bedingungsanweisung testet auf i<5, und die Aktionsanweisung gibt den Wert in i aus und inkrementiert ihn.

Im Rumpf der for-Schleife selbst bleibt nichts weiter zu tun, so daß man hier eine leere Anweisung (;) schreibt. Es sei darauf hingewiesen, daß diese Konstruktion nicht die beste Lösung darstellt. Die Aktionsanweisung ist zu umfangreich. Die folgende Version ist besser geeignet:

8:         for (int i = 0; i<5; i++)
9: cout << "i: " << i << endl;

Beide Versionen bewirken exakt dasselbe, das zweite Beispiel ist aber leichter zu verstehen.

Verschachtelte Schleifen

Befindet sich eine Schleife im Rumpf einer anderen Schleife, spricht man vom Verschachteln von Schleifen. Die innere Schleife wird bei jedem Durchlauf der äußeren vollständig abgearbeitet. Listing 7.14 zeigt ein Beispiel, das Markierungen in einer Matrix mit Hilfe verschachtelter for-Schleifen schreibt.

Listing 7.14: Verschachtelte for-Schleifen

1:   // Listing 7.14
2: // Zeigt verschachtelte for-Schleifen
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int rows, columns;
9: char theChar;
10: cout << "Wie viele Zeilen? ";
11: cin >> rows;
12: cout << "Wie viele Spalten? ";
13: cin >> columns;
14: cout << "Welches Zeichen? ";
15: cin >> theChar;
16: for (int i = 0; i<rows; i++)
17: {
18: for (int j = 0; j<columns; j++)
19: cout << theChar;
20: cout << "\n";
21: }
22: return 0;
23: }

Wie viele Zeilen? 4
Wie viele Spalten? 12
Welches Zeichen? x
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx

Der Anwender wird aufgefordert, die Anzahl der Zeilen (rows) und Spalten (columns) und ein auszugebendes Zeichen einzugeben. Die erste Schleife in Zeile 16 initialisiert einen Zähler i mit 0. Dann beginnt die Ausführung der äußeren Schleife.

In Zeile 18, der ersten Zeile im Rumpf der äußeren for-Schleife, wird eine weitere for-Schleife eingerichtet. Ein zweiter Zähler (j) wird mit 0 initialisiert, und der Rumpf der inneren for-Schleife wird ausgeführt. In Zeile 19 erfolgt die Ausgabe des gewählten Zeichens, und die Steuerung kehrt zum Kopf der inneren for-Schleife zurück. Beachten Sie, daß die innere for-Schleife nur aus einer Anweisung besteht (der Ausgabe des Zeichens). Ergibt der Test der Bedingung j<columns das Ergebnis true, wird j inkrementiert und das nächste Zeichen ausgegeben. Das setzt sich fort, bis j gleich der Anzahl der Spalten (columns) ist.

Sobald der Test der inneren Schleife den Wert false liefert, in diesem Beispiel nach Ausgabe von zwölf x-Zeichen, springt die Ausführung direkt zu Zeile 20, und eine neue Zeile wird begonnen. Die äußere for-Schleife kehrt nun zu ihrem Kopf zurück, wo die Bedingung i<rows getestet wird. Ergibt dieser Test true, wird i inkrementiert und der Rumpf der Schleife ausgeführt.

Im zweiten Durchlauf der äußeren for-Schleife beginnt die innere for-Schleife von neuem. Damit erhält j erneut den Anfangswert 0, und wieder wird die gesamte innere Schleife ausgeführt.

Dem Programm liegt der Gedanke zugrunde, daß in einer verschachtelten Schleife die innere Schleife für jeden Durchlauf der äußeren Schleife ausgeführt wird. Die Anzahl der ausgegebenen Zeichen pro Zeile entspricht damit dem Wert in columns.

Nebenbei bemerkt: Viele C++-Programmierer verwenden die Buchstaben i und j als Zählervariablen. Diese Tradition ist auf FORTRAN zurückzuführen, da dort die Buchstaben i, j, k, l, m und n die einzigen gültigen Zählervariablen waren.

Gültigkeitsbereiche für for-Schleifen

In der Vergangenheit erstreckte sich der Gültigkeitsbereich von Variablen, die in einer for-Schleife deklariert wurden, über den gesamten äußeren Block. Gemäß dem neuen ANSI-Standard wird dies nun anders geregelt. Jetzt beschränkt sich der Gültigkeitsbereich dieser Variablen auf den for-Schleifenblock. Doch diese Änderung wird noch nicht von allen Compilern unterstützt. Mit folgendem Code können Sie Ihren Compiler testen:

#include <iostream.h>
int main()
{
// i gueltig für die for Schleife?
for (int i = 0; i<5; i++)
{
cout << "i: " << i << endl;
}

i = 7; // sollte nicht im Gueltigkeitsbereich liegen!
return 0;
}

Erfolgt die Kompilierung ohne Fehlermeldung, wird der geänderte ANSI-Standard in dieser Hinsicht noch nicht unterstützt.

Merkt Ihr Compiler, daß i noch nicht definiert worden ist (in der Zeile i=7), dann wird der neue Standard von Ihrem Compiler bereits berücksichtigt. Wenn Sie Ihren Code wie folgt umändern, läßt er sich auf beiden Compilern fehlerfrei kompilieren:

#include <iostream.h>
int main()
{
int i; //ausserhalb der for-Schleife deklariert
for (int i = 0; i<5; i++)
{
cout << "i: " << i << endl;
}

i = 7; // Gueltigkeitsbereich korrekt für alle Compiler
return 0;
}

Summierende Schleifen

In Kapitel 5, »Funktionen«, habe ich Ihnen gezeigt, wie Sie das Problem der Fibonacci- Reihe mit Hilfe der Rekursion lösen können. Zur Erinnerung: Eine Fibonacci-Reihe beginnt mit 1,1,2,3, und alle folgenden Zahlen sind Summen der zwei vorhergehenden:

1,1,2,3,5,8,13,21,34 ...

Die n-te Fibonacci-Zahl ist demnach die Summe der Fibonacci-Zahlen n-1 und n-2. In Kapitel 5 haben wir das Problem, für eine bestimmte Fibonacci-Zahl in der Reihe einen Wert zu errechnen, mit Hilfe von Rekursion gelöst. Das Listing 7.15 löst das Problem mittels Iteration.

Listing 7.15: Den Wert einer Fibonacci-Zahl mittels Iteration ermitteln

1:  // Listing 7.15
2: // zeigt wie der Wert der n-ten Fibonacci-Zahl
3: // mittels Iteration ermittelt wird
4:
5: #include <iostream.h>
6:
8:
9: int fib(int position);
10: int main()
11: {
12: int answer, position;
13: cout << "Welche Position? ";
14: cin >> position;
15: cout << "\n";
16:
17: answer = fib(position);
18: cout << answer << " lautet der Wert der ";
19: cout << position << "ten Fibonacci-Zahl.\n";
20: return 0;
21: }
22:
23: int fib(int n)
24: {
25: int minusTwo=1, minusOne=1, answer=2;
26:
27: if (n < 3)
28: return 1;
29:
30: for (n -= 3; n; n--)
31: {
32: minusTwo = minusOne;
33: minusOne = answer;
34: answer = minusOne + minusTwo;
35: }
36:
37: return answer;
38: }

Welche Position? 4
3 lautet der Wert der 4ten Fibonacci-Zahl.
Welche Position? 5
5 lautet der Wert der 5ten Fibonacci-Zahl.
Welche Position? 20
6765 lautet der Wert der 20sten Fibonacci-Zahl.
Welche Position? 100
3314859971 lautet der Wert der 100sten Fibonacci-Zahl.

In Listing 7.15 wird die Fibonacci-Reihe statt durch Rekursion mit Hilfe einer Iteration gelöst. Dieser Ansatz ist schneller und verbraucht wesentlich weniger Speicherplatz als die rekursive Lösung.

Zeile 13 fragt den Anwender nach einer Position, deren Wert ermittelt werden soll. Es ergeht ein Aufruf an die Funktion fib(), die die Position testet. Ist die Position kleiner als 3, liefert die Funktion den Wert 1 zurück. Ab der Position 3 iteriert die Funktion mit folgendem Algorithmus:

  1. Ermittle die Startposition: Initialisiere die Variable answer mit 2, minusTwo mit 1 und minusOne mit 1. Dekrementiere die Position um 3, da die ersten zwei Zahlen von der Startposition abgefangen werden.
  2. Zähle für jede Zahl die Fibonacci-Reihe hoch. Dies geschieht, indem
  1. Wenn n den Wert 0 erreicht, gib die Antwort aus.

Auf diese Art und Weise würden Sie das Problem auch mit Papier und Bleistift lösen. Angenommen Sie sollen den Wert der fünften Fibonacci-Zahl ermitteln. Zuerst würden Sie schreiben:

1, 1, 2,

und denken: »Nur noch zwei.« Dann würden Sie 2+1 addieren, 3 hinschreiben und denken: »Eine Zahl fehlt noch.« Anschließend würden Sie 3+2 schreiben und die Antwort wäre 5. Dabei machen Sie nichts anderes, als bei jedem Durchlauf Ihre Aufmerksamkeit um eine Zahl nach rechts zu rücken und die Zahl, deren Wert gesucht wird, um 1 zu dekrementieren.

Bedenken Sie die Bedingung, die in Zeile 30 getestet wird (n). Dabei handelt es sich um ein C++-Idiom, das n != 0 entspricht. Diese for-Schleife basiert auf der Annahme, daß n zu false getestet wird, wenn es den Wert 0 enthält. Der Kopf der for- Schleife hätte auch wie folgt geschrieben werden können

for (n-=3; n!=0; n--)

was vielleicht eindeutiger gewesen wäre. Dieses Idiom ist jedoch so geläufig in C++, daß es keinen Zweck hat, sich dagegen aufzulehnen.

Kompilieren, linken und starten Sie das Programm zusammen mit dem Programm aus Kapitel 5, das Ihnen eine rekursive Lösung zu dem Problem anbietet. Versuchen Sie einmal, den Wert der 25. Zahl herauszufinden, und vergleichen Sie die Zeit, die jedes Programm benötigt. Rekursion ist zwar eine elegante Lösung, doch da die Funktionsaufrufe auf Kosten der Ausführungszeit gehen und es so viele Funktonsaufrufe bei der Rekursion gibt, ist die Ausführung deutlich langsamer als bei der Iteration.

Microcomputer sind normalerweise für arithmetische Operationen optimal ausgelegt, deshalb sollte der iterative Weg rasend schnell zur Lösung führen.

Sein Sie vorsichtig und geben Sie keine zu große Zahl ein. fib wird sehr schnell sehr groß, und auch Integer vom Typ long laufen irgendwann über.

switch-Anweisungen

In Kapitel 4 haben Sie gelernt, wie man if-Anweisungen und else...if-Anweisungen schreibt. Da man bei zu tief verschachtelten Konstruktionen schnell Gefahr läuft, den Überblick zu verlieren, bietet C++ für solche Konstruktionen eine Alternative. Im Gegensatz zur if-Anweisung, die lediglich einen Wert auswertet, lassen sich mit switch- Anweisungen Verzweigungen in Abhängigkeit von mehreren unterschiedlichen Werten aufbauen. Die allgemeine Form der switch-Anweisung lautet:

switch (Ausdruck)
{
case Wert1: Anweisung;
break;
case Wert2: Anweisung;
break;
....
case WertN: Anweisung;
break;
default: Anweisung;
}

Ausdruck ist jeder gültige C++-Ausdruck. Anweisung steht für beliebige C++-Anweisungen oder Blöcke von Anweisungen, die zu einem Integer-Wert ausgewertet oder zweifelsfrei in einen Integer-Wert konvertiert werden können. Beachten Sie, daß die Auswertung nur auf Gleichheit erfolgt. Man kann weder relationale noch Boole'sche Operatoren verwenden.

Wenn einer der case-Werte mit dem Ausdruck übereinstimmt, springt die Ausführung zu diesen Anweisungen und arbeitet sie bis zum Ende des switch-Blocks oder bis zur nächsten break-Anweisung ab. Läßt sich keine Übereinstimmung ermitteln, verzweigt die Ausführung zur optionalen default-Anweisung. Ist kein default-Zweig vorhanden und gibt es keinen übereinstimmenden Wert, kommt es überhaupt nicht zur Abarbeitung von Anweisungen in der switch-Konstruktion - die Anweisung ist damit beendet.

Es empfiehlt sich, in switch-Anweisungen immer einen default-Zweig vorzusehen. Auch wenn man diesen Zweig eigentlich nicht benötigt, kann man hier zumindest auf angeblich unmögliche case-Fälle testen und eine Fehlermeldung ausgeben. Damit läßt sich die Fehlersuche oft erheblich beschleunigen.

Wichtig ist besonders die break-Anweisung am Ende eines case-Zweiges. Wenn diese fehlt, geht das Programm zur Ausführung des nächsten case-Zweiges über. Manchmal ist das zwar gewollt, gewöhnlich handelt es sich aber um einen Fehler. Falls Sie die Programmausführung tatsächlich mit dem nächsten case-Zweig fortsetzen möchten, sollten Sie mit einem Kommentar darauf hinweisen, daß die break-Anweisung nicht vergessen wurde.

Listing 7.16 zeigt ein Beispiel für die switch-Anweisung.

Listing 7.16: Einsatz der switch-Anweisung

1:  // Listing 7.16
2: // Zeigt die switch-Anweisung
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short int number;
9: cout << "Bitte eine Zahl zwischen 1 und 5 eingeben: ";
10: cin >> number;
11: switch (number)
12: {
13: case 0: cout << "Leider zu klein!";
14: break;
15: case 5: cout << "Gut!\n"; // Weiter mit naechstem case
16: case 4: cout << "Sehr gut!\n"; // Weiter mit naechstem case
17: case 3: cout << "Ausgezeichnet!\n"; // Weiter mit naechstem case
18: case 2: cout << "Meisterhaft!\n"; // Weiter mit naechstem case
19: case 1: cout << "Unglaublich!\n";
20: break;
21: default: cout << "Zu gross!\n";
22: break;
23: }
24: cout << "\n\n";
25: return 0;
26: }

Bitte eine Zahl zwischen 1 und 5 eingeben:  3
Ausgezeichnet!
Meisterhaft!
Unglaublich!

Bitte eine Zahl zwischen 1 und 5 eingeben: 8
Zu gross!

Das Programm fragt zunächst eine Zahl vom Anwender ab und wertet sie dann in der switch-Anweisung aus. Ist die Zahl gleich 0, stimmt das mit der case-Anweisung in Zeile 13 überein. Es erscheint die Meldung Leider zu klein!, und die break-Anweisung beendet die switch-Struktur. Wenn der Wert gleich 5 ist, springt die Programmausführung zu Zeile 15, wo eine Meldung ausgegeben wird. Dann wird die Ausführung mit Zeile 16 fortgesetzt, und es wird eine weitere Meldung ausgegeben. Das geht so lange, bis die break-Anweisung in Zeile 20 erreicht ist.

Im Endeffekt erscheinen bei Zahlen zwischen 1 und 5 mehrere Meldungen auf dem Bildschirm. Wenn die Zahl nicht im Bereich zwischen 0 und 5 liegt, gilt sie im Programm als zu groß. Diesen Fall behandelt die default-Anweisung in Zeile 21.

Die switch-Anweisung

Die Syntax der switch-Anweisung lautet wie folgt:

switch (Ausdruck)
{
case Wert1: Anweisung;
case Wert2: Anweisung;
....
case WertN: Anweisung;
default: Anweisung;
}

Mit der switch-Anweisung kann man Verzweigungen in Abhängigkeit von mehreren Werten des Ausdrucks aufbauen. Der Ausdruck wird ausgewertet, und wenn er einem der case-Werte entspricht, springt die Programmausführung in diese Zeile. Die Ausführung wird fortgesetzt, bis entweder das Ende der switch-Anweisung oder eine break-Anweisung erreicht wird.

Stimmt der Ausdruck mit keinem der case-Zweige überein und es ist eine default-Anweisung vorhanden, springt die Programmausführung zu diesem default-Zweig. Andernfalls endet die switch-Anweisung.

Beispiel 1:

switch (choice)
{
case 0:
cout << "Null!" << endl;
break;
case 1:
cout << "Eins!" << endl;
break;

case 2:
cout << "Zwei!" << endl;
default:
cout << "Standard!" << endl;
}

Beispiel 2:

switch (choice)
{
case 0:
case 1:
case 2:
cout << "Kleiner als 3!";
break;
case 3:
cout << "Gleich 3!";
break;
default:
cout << "Groesser als 3!";
}

switch-Anweisung und Menüs

Mit Listing 7.17 kehren wir zurück zu den schon vorher besprochenen for-Schleifen (;;). Diese Schleifen werden auch forever-Schleifen oder Endlosschleifen genannt, da sie endlos durchlaufen werden, bis das Programm auf ein break trifft. Die forever- Schleife wird häufig verwendet, um Menüs einzurichten, den Anwender aufzufordern, eine Auswahl zu treffen, auf die Auswahl zu reagieren und dann zu dem Menü zurückzukehren. Diese Schleife wird fortgesetzt, bis der Anwender sich entscheidet, die Schleife zu verlassen.

Einige Programmierer ziehen folgende Schreibweise vor

#define EVER ;;
for (EVER)
{
// Anweisungen ...

Eine forever-Schleife ist eine Schleife ohne Abbruchbedingung. Schleifen dieser Art kann man nur über eine break-Anweisung verlassen. forever-Schleifen werden auch als Endlosschleifen bezeichnet.

Listing 7.17:  Eine forever-Schleife

1:    //Listing 7.17
2: //forever-Schleife zum Abfragen
3: //von Anwenderbefehlen
4: #include <iostream.h>
5:
6: // Prototypen
7: int menu();
8: void DoTaskOne();
9: void DoTaskMany(int);
10:
11: int main()
12: {
13:
14: bool exit = false;
15: for (;;)
16: {
17: int choice = menu();
18: switch(choice)
19: {
20: case (1):
21: DoTaskOne();
22: break;
23: case (2):
24: DoTaskMany(2);
25: break;
26: case (3):
27: DoTaskMany(3);
28: break;
29: case (4):
30: continue; // ueberfluessig!
31: break;
32: case (5):
33: exit=true;
34: break;
35: default:
36: cout << "Bitte erneut auswaehlen!\n";
37: break;
38: } // end switch
39:
40: if (exit)
41: break;
42: } // Ende von forever
43: return 0;
44: } // Ende von main()
45:
46: int menu()
47: {
48: int choice;
49:
50: cout << " **** Menue ****\n\n";
51: cout << "(1) Auswahl Eins.\n";
52: cout << "(2) Auswahl Zwei.\n";
53: cout << "(3) Auswahl Drei.\n";
54: cout << "(4) Menue erneut anzeigen.\n";
55: cout << "(5) Beenden.\n\n";
56: cout << ": ";
57: cin >> choice;
58: return choice;
59: }
60:
61: void DoTaskOne()
62: {
63: cout << "Aufgabe Eins!\n";
64: }
65:
66: void DoTaskMany(int which)
67: {
68: if (which == 2)
69: cout << "Aufgabe Zwei!\n";
70: else
71: cout << "Aufgabe Drei!\n";
72: }

**** Menue ****

(1) Auswahl Eins.
(2) Auswahl Zwei.
(3) Auswahl Drei.
(4) Menue erneut anzeigen.
(5) Beenden.

: 1
Aufgabe Eins!
**** Menue ****
(1) Auswahl Eins.
(2) Auswahl Zwei.
(3) Auswahl Drei.
(4) Menue erneut anzeigen.
(5) Beenden.

: 3
Aufgabe Drei!
**** Menue ****
(1) Auswahl Eins.
(2) Auswahl Zwei.
(3) Auswahl Drei.
(4) Menue erneut anzeigen.
(5) Beenden.

: 5

In diesem Programm fließen eine Reihe der heute und in den letzten Tagen vorgestellten Konzepte zusammen. Daneben finden Sie auch einen häufigen Einsatz der switch-Anweisung.

In Zeile 15 startet eine forever-Schleife. Die menu()-Funktion wird aufgerufen. Sie gibt das Menü auf dem Bildschirm aus und liefert die vom Anwender gewählte Option zurück. Die switch-Anweisung, die in Zeile 18 beginnt und in Zeile 38 endet, verzweigt je nach der Auswahl des Anwenders.

Gibt der Anwender 1 ein, springt die Ausführung zu der Anweisung case 1: in Zeile 20. Zeile 21 leitet die Ausführung dann zu der Funktion doTaskOne(), die eine Meldung ausgibt und zurückkehrt. Nachdem die Funktion zurückgekehrt ist, wird die Ausführung in Zeile 22 fortgesetzt. Dort beendet der break-Befehl die switch-Anweisung, und das Programm wird dann erst mit Zeile 39 fortgesetzt. Zeile 40 wertet die Variable exit aus. Ergibt sie true, wird der break-Befehl in Zeile 41 ausgeführt und die for-Schleife (;;) wird verlassen. Ergibt sie hingegen false, springt die Ausführung wieder zurück zum Anfang der Schleife in Zeile 15.

Beachten Sie, daß die continue-Anweisung in Zeile 30 an sich überflüssig ist. Hätte man sie weggelassen, würde als nächstes die break-Anweisung ausgeführt, die switch- Anweisung würde verlassen, exit würde als false ausgewertet, die Schleife würde erneut durchlaufen und das Menü würde erneut ausgegeben. Mit continue können Sie jedoch den Test von exit umgehen.

Was Sie tun sollten

... und was nicht

Verwenden Sie switch-Anweisungen, um stark verschachtelte if-Anweisungen zu vermeiden.

Dokumentieren Sie sorgfältig alle case-Zweige, die absichtlich ohne break aufgesetzt werden.

Nehmen Sie in Ihre switch-Anweisungen eine default-Anweisung mit auf, und sei es auch nur, um anscheinend unmögliche Situationen festzustellen.

Achten Sie darauf, die break-Anweisung am Ende eines case-Zweiges zu setzen, wenn die Ausführung nicht mit dem nächsten Zweig fortgesetzt werden soll.

Zusammenfassung

In C++ gibt es mehrere Möglichkeiten für die Realisierung von Schleifen. while- Schleifen prüfen eine Bedingung. Ergibt dieser Test das Ergebnis true, werden die Anweisungen im Rumpf der Schleife ausgeführt. do...while-Schleifen führen den Rumpf der Schleife aus und testen danach die Bedingung. for-Schleifen initialisieren einen Wert und testen dann einen Ausdruck. Wenn der Ausdruck gleich true ist, werden die letzte Anweisung im Kopf der for-Schleife und ebenso der Rumpf der Schleife ausgeführt. Nach jedem Durchlauf testet die Schleifenanweisung den Ausdruck erneut.

Auf die goto-Anweisung verzichtet man im allgemeinen, da sie unbedingte Sprünge zu einer scheinbar willkürlichen Stelle im Code ausführen kann, die einen unübersichtlichen und schwer zu wartenden Code erzeugen. Mit der continue-Anweisung kann man while-, do...while- und for-Schleifen sofort von vorn beginnen, während die break-Anweisung zum Verlassen von while-, do....while-, for- und switch-Anweisungen führt.

Fragen und Antworten

Frage:
Wie wählt man zwischen if...else und switch aus?

Antwort:
Wenn mehr als ein oder zwei else-Klauseln vorhanden sind und alle denselben Ausdruck testen, sollte man eine switch-Anweisung verwenden.

Frage:
Wie wählt man zwischen while und do...while aus?

Antwort:
Wenn der Rumpf der Schleife zumindest einmal auszuführen ist, verwendet man eine do...while-Schleife. Andernfalls kann man mit einer while-Schleife arbeiten.

Frage:
Wie wählt man zwischen while und for aus?

Antwort:
Wenn man eine Zählvariable initialisiert, diese Variable testet und sie bei jedem Schleifendurchlauf inkrementiert, kommt eine for-Schleife in Frage. Ist die Variable bereits initialisiert und wird nicht bei jedem Schleifendurchlauf inkrementiert, kann eine while-Schleife die bessere Wahl darstellen.

Frage:
Wie wählt man zwischen Rekursion und Iteration aus?

Antwort:
Einige Probleme fordern direkt eine Rekursion. Doch die meisten Probleme lassen sich auch mittels Iteration lösen. Behalten Sie die Möglichkeit der Rekursion im Hinterkopf, sie kann manchmal ganz nützlich sein.

Frage:
Ist es besser, while(true) oder for (;;) zu verwenden?

Antwort:
Hier gibt es keinen wesentlichen Unterschied.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösungen in Anhang D lesen und zur Lektion des nächsten Tages übergehen.

Quiz

  1. Wie initialisiert man mehr als eine Variable in einer for-Schleife?
  2. Warum sollte man goto vermeiden?
  3. Ist es möglich, eine for-Schleife zu schreiben, deren Rumpf niemals ausgeführt wird?
  4. Ist es möglich, while-Schleifen in for-Schleifen zu verschachteln?
  5. Ist es möglich, eine Schleife zu erzeugen, die niemals endet? Geben Sie ein Beispiel.
  6. Was passiert, wenn Sie eine Endlosschleife erzeugt haben?

Übungen

  1. Wie lautet der Wert von x, wenn die folgende for-Schleife durchlaufen ist?
    for (int x = 0; x < 100; x++)
  2. Schreiben Sie eine verschachtelte for-Schleife, die ein Muster von 10 x 10 Nullen (0) ausgibt.
  3. Schreiben Sie eine for-Anweisung, die in Zweierschritten von 100 bis 200 zählt.
  4. Schreiben Sie eine while-Schleife, die in Zweierschritten von 100 bis 200 zählt.
  5. Schreiben Sie eine do...while-Schleife, die in Zweierschritten von 100 bis 200 zählt.
  6. FEHLERSUCHE: Was ist falsch an folgendem Code?
    int counter = 0;
    while (counter < 10)
    {
    cout << "Zaehler: " << counter;
    }
  7. FEHLERSUCHE: Was ist falsch an folgendem Code?
    for (int counter = 0; counter < 10; counter++);
    cout << counter << " ";
  8. FEHLERSUCHE: Was ist falsch an folgendem Code?
    int counter = 100;
    while (counter < 10)
    {
    cout << "Zaehler: " << counter;
    counter--;
    }
  9. FEHLERSUCHE: Was ist falsch an folgendem Code?
    cout << "Geben Sie eine Zahl zwischen 0 und 5 ein: ";
    cin >> theNumber;
    switch (theNumber)
    {
    case 0:
    doZero();
    case 1: // Weiter mit nächstem case
    case 2: // Weiter mit nächstem case
    case 3: // Weiter mit nächstem case
    case 4: // Weiter mit nächstem case
    case 5:
    doOneToFive();
    break;
    default:
    doDefault();
    break;
    }



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH