C++-Programmierung/ Einführung in C++/ Schleifen

Aus Wikibooks


Mit dem, was Sie bis jetzt gelernt haben, sollte es für Sie eine leichte Übung sein, die Zahlen von eins bis zehn ausgeben zu lassen. So könnte ein Programm aussehen, das dies tut:

#include <iostream>

int main() {
    std::cout << "1\n";
    std::cout << "2\n";
    std::cout << "3\n";
    std::cout << "4\n";
    std::cout << "5\n";
    std::cout << "6\n";
    std::cout << "7\n";
    std::cout << "8\n";
    std::cout << "9\n";
    std::cout << "10\n";
}
Ausgabe:
1
2
3
4
5
6
7
8
9
10

Dieses Programm ist einfach – aber was wäre, wenn die Zahlen eins bis einer Million ausgegeben werden sollen? Oder schlimmer noch – ja, es geht noch schlimmer: die Ausgabe hängt von der Benutzereingabe ab. Dann müssten Sie von der größtmöglichen Zahl ausgehen (bei unsigned int üblicherweise 4.294.967.295) und zusätzlich noch nach jeder Ausgabe überprüfen, ob die vom Benutzer eingegebene Zahl erreicht ist.

#include <iostream>

int main() {
    unsigned i = 1;                  // 2 Variablen anlegen
    unsigned benutzer = 0;

    std::cin >> benutzer;            // Benutzer gibt Zahl ein

    if (i <= benutzer) {             // Benutzereingabe erreicht?
        std::cout << "1\n";          // Ausgabe der Zahl 1
        ++i;                         // Ausgegebene Zahlen mitzählen
    } else {
        return 0;                    // Anwendung beenden
    }

    if (i <= benutzer) {             // Benutzereingabe erreicht?
        std::cout << "2\n";          // Ausgabe der Zahl 2
        ++i;                         // Ausgegebene Zahlen mitzählen
    } else {
        return 0;                    // Anwendung beenden
    }

    // ...

    if (i <= benutzer) {             // Benutzereingabe erreicht?
        std::cout << "4294967295\n"; // Ausgabe der Zahl 4294967295
        ++i;                         // Ausgegebene Zahlen mitzählen
    } else {
        return 0;                    // Anwendung beenden
    }

    // Wenn Ihr Compiler diese Stelle erreichen soll, brauchen Sie einen leistungsstarken
    // Rechner und ein robustes Betriebssystem.
    // Um dieses Problem zu lösen, setzen Sie sich einfach in die nächste Zeitmaschine
    // und bestellen Sie sich einen Rechner aus dem Jahr 2030!
}

Insgesamt würden sich über 17,1 Milliarden Zeilen Code ergeben.

An diesem Punkt kommen Schleifen ins Spiel. Bis jetzt wurden alle Programme einfach der Reihe nach abgearbeitet und zwischendurch wurde eventuell mal verschiedenen Zweigen gefolgt. Mit einer Schleife können Sie erreichen, dass ein Programmteil mehrfach abgearbeitet wird. C++ stellt drei Schleifenkonstrukte zur Verfügung. Die kopfgesteuerte while-Schleife, die fußgesteuerte do-while-Schleife und die (ebenfalls kopfgesteuerte) for-Schleife. Was kopf- und fußgesteuerte Schleife bedeutet erfahren Sie in Kürze. Vielleicht wissen Sie es aber auch schon aus dem Kapitel „Grundlegende Elemente“, aus dem Abschnitt „Für Programmieranfänger“.

Die while-Schleife[Bearbeiten]

Eine while-Schleife hat die folgende allgemeine Form:

Syntax:
while(«Bedingung») «Anweisung»
«Nicht-C++-Code», »optional«

Natürlich können Sie, wie bei den Verzweigungen auch, hier wieder mehrere Anweisungen zu einem Anweisungsblock zusammenfassen, da diese als eine Anweisung gilt:

Syntax:
while(«Bedingung») {
    «Anweisungen»
}
«Nicht-C++-Code», »optional«

Solange die Bedingung erfüllt ist, wird die Anweisung oder der Anweisungsblock ausgeführt. Da es sich hier um eine kopfgesteuerte Schleife handelt, wird erst die Bedingung ausgewertet. Ist diese erfüllt, so wird dann die Anweisung ausgeführt. Ist die nächste Überprüfung der Bedingung positiv, so wird der Schleifenrumpf erneut ausgeführt und so fort. Ist die Bedingung nicht erfüllt, wird der Schleifeninhalt übersprungen und mit dem Quelltext nach der Schleife fortgesetzt. Es kann daher vorkommen, dass der Schleifenrumpf gar nicht ausgeführt wird - wenn die Bedingung schon zu Beginn nicht erfüllt wird.

Was eine fußgesteuerte Schleife macht, erfahren Sie unter der Überschrift do-while. Eine Gegenüberstellung der beiden Schleifen gibt es in der Zusammenfassung dieses Kapitels.

Tipp

Wie Bedingungen ausgewertet werden, können Sie im Kapitel „Verzweigungen“ nachlesen - das vorherige Kapitel.

Hinweis

Beachten Sie, dass Schleifen so lange ausgeführt werden, bis die Bedingung nicht mehr erfüllt ist. Wenn Sie also nicht innerhalb der Schleife dafür sorgen, dass die Bedingung irgendwann nicht mehr erfüllt ist, dann haben Sie eine sogenannte Endlosschleife. Das heißt, der Schleifenrumpf (so nennt man die Anweisung oder den Anweisungsblock einer Schleife) wird immer wieder ausgeführt. Das Programm kann nur durch Abbrechen beendet werden. In Kombination mit einem Schlüsselwort, das Sie in Kürze kennen lernen werden, kann eine solche Endlosschleife durchaus gewollt und sinnvoll sein (der Befehl kann die Schleife abbrechen), aber in der Regel entsteht so etwas versehentlich. Wenn Ihr Programm also mal „abgestürzt“ ist, im Sinne von „Es reagiert nicht mehr! Wie schrecklich!“, dann haben Sie vermutlich irgendwo eine Endlosschleife eingebaut.

Nun aber zurück zu unserer Schleifenaufgabe mit 17,1 Milliarden Zeilen Code. Da Ihr Compiler immer noch nicht damit fertig ist, die oben vorgestellte Lösung zu übersetzen, versuchen wir jetzt mal das ganze mit einer while-Schleife zu lösen.

#include <iostream>

int main() {
    unsigned i = 1;                  // 2 Variablen anlegen
    unsigned benutzer = 0;

    std::cin >> benutzer;            // Benutzer gibt Zahl ein

    while (i <= benutzer) {          // Benutzereingabe erreicht?
        std::cout << i << std::endl; // Ausgabe von i und einem Zeilenumbruch
        i++;                         // i um eins erhöhen (-> ausgegebene Zahlen mitzählen)
    }
}

Nun ja, das sieht dem Programm von oben doch irgendwie ähnlich, nur die knapp 4,3 Milliarden if-Anweisungen sind weggefallen und haben einer vom Aufbau fast identischen while-Schleife Platz gemacht. Nun haben wir natürlich ein Problem: Ihr Superrechner aus dem Jahr 2020 ist immer noch mit der Übersetzung der ersten Programmversion beschäftigt und wird es wohl auch noch bis zu seiner Erfindung bleiben. Aber zum Glück haben Sie ja noch einen alten Rechner von 1980. Also versuchen Sie das Programm auf diesem zu übersetzen und auszuführen. Tatsächlich. Es funktioniert. Erstaunlich, dass so eine alte Kiste einen Rechner überholt, den Sie sich extra aus der Zukunft haben liefern lassen, um die maximale Leistungsstärke zu bekommen.

Wenn Sie Schwierigkeiten haben das Beispiel nachzuvollziehen, dann sehen Sie sich noch mal die Schleifen-Version für die Zahlen von 1 bis 10 an.

#include <iostream>

int main() {
    int i = 1;                       // Anlegen von i

    while (i <= 10) {                // Ist i noch kleiner-gleich 10?
        std::cout << i << std::endl; // Ausgabe von i und neue Zeile
        i++;                         // i um eins erhöhen
    }
}
Ausgabe:
1
2
3
4
5
6
7
8
9
10

So sieht das Ganze schon viel kompakter und vielleicht auch übersichtlicher aus als die Version von ganz oben. Die Ausgabe dagegen ist völlig identisch. Hier wird i am Anfang auf eins gesetzt. Somit ist die Bedingung (eins ist kleiner oder gleich zehn) erfüllt. Damit wird der Schleifenrumpf ausgeführt. „1“ wird ausgegeben. Es folgt ein Zeilenumbruch. Danach wird der Wert von i um eins erhöht. Danach wird wieder geprüft, ob die Bedingung erfüllt ist. Da i jetzt zwei ist, lautet die Bedingung „zwei ist kleiner oder gleich zehn“, da dies eine wahre Aussage ist, wird wieder der Schleifenrumpf ausgeführt. Das wiederholt sich bis i Schließlich den Wert elf hat. Die Bedingung lautet dann „elf ist kleiner oder gleich zehn“, diese Aussage ist zweifellos falsch. Daher wird der Schleifenrumpf nun übersprungen und mit dem Code dahinter weitergemacht. Da in unserem Beispiel dort aber kein Code mehr folgt, wird das Programm beendet.

Zusammengefasst:

  1. Quelltext vor der Schleife
  2. Schleifen Bedingung
    • Erfüllt:
      1. Schleifenrumpf
      2. weiter mit 2
    • Nicht erfüllt:
      1. weiter mit 3
  3. Quelltext nach der Schleife

Die do-while-Schleife[Bearbeiten]

Wie versprochen lüften wir nun das Geheimnis um die fußgesteuerten Schleifen. do-while ist eine fußgesteuerte Schleife, das heißt, als erstes wird der Schleifenrumpf ausgeführt, danach die Bedingung überprüft und dann abhängig von der Bedingung, wieder der Rumpf ausgeführt (Bedingung erfüllt) oder mit dem Quelltext nach der Schleife fortgesetzt (Bedingung nicht erfüllt). Eine fußgesteuerte Schleife zeichnet sich also dadurch aus, dass der Schleifenrumpf mindestens ein mal ausgeführt wird.

Syntax:
do «Anweisung» while(«Bedingung»);
«Nicht-C++-Code», »optional«

Bei dieser Schleife finden wir die obige Syntax nur selten, da der Schleifenrumpf hier zwischen den beiden Schlüsselwörtern do und while steht, wird fast immer ein Anweisungsblock benutzt, auch wenn nur eine Anweisung vorhanden ist. Für Ihren Compiler spielt das natürlich keine Rolle, aber für einen Menschen der den Quelltext liest, ist es übersichtlicher.

Syntax:
do{
    «Anweisungen»
} while(«Bedingung»);
«Nicht-C++-Code», »optional«

Unser anfängliches Riesenprogramm sieht mit einer do-while Schleife so aus:

#include <iostream>

int main() {
    unsigned i = 1;                  // 2 Variablen anlegen
    unsigned benutzer = 0;

    std::cin >> benutzer;            // Benutzer gibt Zahl ein

    do {                             // Schleifenanfang
        std::cout << i << std::endl; // Ausgabe von i
        ++i;                         // Ausgegebene Zahlen mitzählen
    } while (i <= benutzer);         // Benutzereingabe erreicht?
}

Sie werden feststellen, dass die Ausgabe dieses Programms mit der Ausgabe in der while-Schleifen Version übereinstimmt. Den Unterschied bemerken Sie, wenn Sie 0 eingeben: Während die while-Version keine Ausgabe macht, gibt diese do-while-Version „1“ und einen Zeilenumbruch aus, denn der Schleifenrumpf wird immer erst einmal ausgeführt, erst danach wird die Bedingung überprüft und entschieden ob er noch einmal ausgeführt werden muss.

Zusammengefasst:

  1. Quelltext vor der Schleife
  2. Schleifenrumpf
  3. Schleifen Bedingung
    • Erfüllt: weiter mit 2
    • Nicht erfüllt: weiter mit 4
  4. Quelltext nach der Schleife

Die for-Schleife[Bearbeiten]

Die for-Schleife ist etwas komplexer als die vorherigen beiden Schleifen. Sie gliedert sich in Teile:

Syntax:
for(«Initialisierungsteil»; «Bedingungsteil»; «Anweisungsteil») «Schleifenrumpf»
«Nicht-C++-Code», »optional«

Der Schleifenrumpf kann wie immer eine einzelne Anweisung oder ein {}-Anweisungsblock sein. Der Bedingungsteil verhält sich genau wie bei der while- und der do-while-Schleife oder sagen wir fast genau so, denn einen kleinen aber feinen Unterschied gibt es doch. Während while und do-while immer eine Bedingung erwarten, muss bei einer for-Schleife nicht unbedingt eine Bedingung angegeben werden. Wenn keine Bedingung angegeben ist, wird einfach angenommen, dass die Bedingung immer erfüllt ist, Sie erhalten eine Endlosschleife. Wie das sinnvoll eingesetzt wird erfahren Sie in Kürze.

Im Anweisungsteil können Sie eine beliebige Anweisung ausführen, dieser Teil wird oft verwendet, um Variablen bei jedem Schleifendurchlauf hoch oder runter zu zählen. Im nächsten Beispiel wird dies auch demonstriert. Es ist auch möglich, mehrere solcher „hoch- oder runter-Zähl“-Anweisungen durch Komma getrennt anzugeben, das wird im nächsten Beispiel aber nicht gemacht. Der Anweisungsteil wird übrigens direkt nach dem Schleifenrumpf und vor dem nächsten Bedingungstest ausgeführt. Der Initialisierungsteil ist dem Anweisungsteil dahingehend ähnlich, als dass auch hier eine beliebige Anweisung ausgeführt werden kann. Zusätzlich ist es hier aber noch möglich Variablen eines Datentyps anzulegen. Sie können also problemlos 2 int-Variablen anlegen, aber nicht eine int- und eine char-Variable. Der Initialisierungsteil wird nur einmal am Beginn der Schleife ausgeführt.

Thema wird später näher erläutert…

Im Kapitel Lebensdauer und Sichtbarkeit von Objekten werden Sie noch etwas genaueres darüber erfahren wo Sie die im Initialisierungsteil der Schleife angelegten Variablen verwenden können. Dort werden Sie auch erfahren wie eine for-Schleife mit Hilfe einer while-Schleife „nachgebaut“ werden kann.

Jetzt sollten Sie sich jedoch merken, dass Sie eine solche Variable nur innerhalb der Schleife verwenden können, also nicht mehr nach ihrem Verlassen.

#include <iostream>

int main() {
    unsigned benutzer = 0;                   // Variablen für Benutzereingabe

    std::cin >> benutzer;                    // Benutzer gibt Zahl ein

    for(unsigned i = 1; i <= benutzer; ++i)  // for-Schleife
        std::cout << i << std::endl;         // Ausgabe von i
}

Zusammengefasst:

  1. Quelltext vor der Schleife
  2. Initialisierungsteil der Schleife
  3. Schleifen Bedingung
    • Erfüllt:
      a) Schleifenrumpf
      b) Anweisungsteil
      c) weiter mit 3
    • Nicht erfüllt: weiter mit 4
  4. Quelltext nach der Schleife
Thema wird später näher erläutert…

Seit C++11 gibt noch eine weitere Form der for-Schleife. In vielen anderen Programmiersprachen nennt man diese Form die »foreach«-Schleife:

Syntax:
for(«Element»: «Container») «Schleifenrumpf»
«Nicht-C++-Code», »optional«

An dieser Stelle des Buches lässt sich die Funktionsweise noch schwer erklären, da die Datenstrukturen (welche hier mit «Container» angedeutet sind) noch nicht behandelt wurden. Daher wird erst später genauer auf diese Form der for-Schleife eingegangen.

Zu erkennen ist sie daran, das es keine zwei Semikolons zwischen den Klammern gibt, sondern einen Doppelpunkt.

Die break-Anweisung[Bearbeiten]

Jetzt ist es an der Zeit, das Geheimnis um die sinnvolle Verwendung von Endlosschleifen zu enthüllen. Die break-Anweisung wird innerhalb von Schleifen verwendet, um die Schleife sofort zu beenden. Der Quelltext wird dann ganz normal nach der Schleife fortgesetzt. break können Sie in jeder der drei Schleifen verwenden. Das folgende Beispiel demonstriert den Einsatz von break, anhand unseres Lieblingsprogramms in diesem Kapitel.

#include <iostream>

int main() {
    unsigned i = 1;                // 2 Variablen anlegen
    unsigned benutzer = 0;

    std::cin >> benutzer;          // Benutzer gibt Zahl ein

    for(;;){                       // Endlos-for-Schleife
        std::cout << i << "\n";    // Ausgabe von i
        ++i;                       // Variable erhöhen
        if(i > benutzer) break;    // Abbrechen, wenn Bedingung erfüllt
    }
}

Sie können aus jeder Schleife eine Endlosschleife machen, hier wurde die for-Schleife gewählt um zu zeigen wie Sie ohne Bedingung aussieht. Bei einer while- oder do-while-Schleife könnten Sie beispielsweise true als Bedingung angeben. Die for-Schleife legt jetzt übrigens das gleiche Verhalten an den Tag, wie eine do-while-Schleife. Sie können auch problemlos nur einen oder zwei Teile des Schleifenkopfes bei for-Schleife übergeben, wichtig ist nur, dass Sie die beiden Semikolons immer angeben, da Sie dem Compiler mitteilen, was welcher Teil des Schleifenkopfes ist.

Nun wissen Sie wieder etwas mehr über for, dabei sollte es unter dieser Überschrift doch eigentlich um break gehen. Wie Sie aber sehen, hängt letztlich alles mit allem zusammen und so ist es oft schwer eine richtige Abgrenzung zu schaffen.

Die continue-Anweisung[Bearbeiten]

Das zweite wichtige Schlüsselwort für Schleifen ist continue. Es wird genau so verwendet wie break, bricht die Schleife allerdings nicht völlig ab, sondern setzt die Codeausführung am Ende des Schleifenrumpfes fort. Für while und do-while bedeutet das beim Bedingungstest, für die for-Schleife beim Anweisungsteil. Mit continue können Sie also den Rest des aktuellen Schleifendurchlaufs überspringen. Wir sehen uns das wieder anhand des Beispiels an.

#include <iostream>

int main() {
    unsigned benutzer = 0;                    // Variablen für Benutzereingabe

    std::cin >> benutzer;                     // Benutzer gibt Zahl ein

    for(unsigned i = 1; i <= benutzer; ++i) { // for-Schleife
        if (i % 2 == 0) continue;             // alle geraden Zahlen überspringen
        std::cout << i << std::endl;          // Ausgabe von i
    }
}

Hier werden nur ungrade Zahlen ausgegeben, da der %-Operator (liefert den Rest einer Ganzzahligen Division) bei allen geraden Zahlen, (teilt man durch 2 ist als Rest ja nur 0 oder 1 möglich,) 0 zurückliefert und somit continue ausgeführt wird.

Natürlich könnten Sie das auch noch auf eine andere Weise realisieren. Aber es gibt ja beim Programmieren viele Wege, die nach Rom führen, wie in diesem Kapitel anhand der verschieden Schleifen schon bewiesen wurde. Leider gibt es aber noch mehr Wege, die auf direktem Wege an Rom vorbeiführen... Aber hier ist für das Beispiel von eben noch ein Pfad, der sicher nach Rom führt:

#include <iostream>

int main() {
    unsigned int benutzer = 0;                 // Variablen für Benutzereingabe

    std::cin >> benutzer;                      // Benutzer gibt Zahl ein

    for(unsigned i = 1; i <= benutzer; i += 2) // for-Schleife mit 2er Schritten
        std::cout << i << std::endl;           // Ausgabe von i
}

Strukturierte Programmierung[Bearbeiten]

Die Anweisungen 'break' und 'continue' gelten als „unsauber“, sie erschweren fast immer das Verständnis des Programms. Die Verwendung von 'break' ist oft ein Zeichen dafür, dass die Schleifenbedingung unvollständig ist - und man daher nicht einzig aus der Schleifenbedingung ersehen kann, wie lange die Schleife läuft. Die 'continue'-Anweisung kann meist durch eine 'if'-Anweisung ersetzt werden - oder ebenfalls durch eine exaktere Schleifenbedingung. In als „gut“ erachteten Programmen kommen 'break' und 'continue' möglichst gar nicht vor.

Kapitelanhang[Bearbeiten]

Im Anhang zu diesem Kapitel finden Sie:

  • Aufgaben und zugehörige Musterlösungen.
Aufgaben
Aufgabe 1: Schreiben Sie mithilfe einer Schleife ein kleines Spiel. Der Spielablauf lautet:
  • Am Anfang gibt der Spieler einen Zahlenbereich ein. (Zum Beispiel: 1-100)
  • Der Spieler muss sich innerhalb dieses Bereiches eine Zahl merken (eingegebene Grenzzahlen sind nicht zulässig).
  • Das Programm soll dann die Zahl erraten. Der Benutzer teilt dem Programm mit, ob die Zahl an die er denkt kleiner, größer oder gleich der vom Programm geratenen Zahl ist. Die kann zum Beispiel über die Eingabe von <, > und = erfolgen.

Es gibt natürlich viele Wege dieses Problem zu lösen und Sie sehen ja ob Ihr Programm funktioniert oder nicht. Hier wird nur eine Musterlösung vorgestellt, falls Sie überhaupt nicht zurechtkommen oder sich einfach dafür interessieren wie der Autor an das Problem herangegangen ist.

#include <iostream>

int main() {
    int min, max; // Variablen für den möglichen Zahlenbereich
    int zahl;     // Zahl die der Rechner vermutet

    std::cout << "Wo fängt die Zahlenreihe an?: ";     // Zahlenbereich abfragen
    std::cin >> min;                                   // Benutzereingabe einlesen

    std::cout << "Wo hört die Zahlenreihe auf?: ";     // Zahlenbereich abfragen
    std::cin >> max;                                   // Benutzereingabe einlesen

    for (char eingabe = '0'; eingabe != '=';) {        // Abbrechen wenn eingabe '=' ist
        zahl = min + (max - min) / 2;                  // Mittlere Zahl berechnen
        std::cout << "Denken Sie an " << zahl << "? "; // Vermutung ausgeben
        std::cin >> eingabe;                           // Antwort einlesen

        if (eingabe == '<')                        // Ist die Zahl kleiner?
            max = zahl;                            // Setzte max auf den zu großen Wert zahl
        else if (eingabe == '>')                   // Ist die Zahl größer?
            min = zahl;                            // Setzte min auf den zu kleinen Wert zahl
        else if (eingabe != '=')                   // Ist Eingabe auch kein Gleichheitszeichen
            std::cout << "Sie haben ein unzulässiges Zeichen eingegeben!\n"; // Fehlerhafte Eingabe melden

        if (min+1 >= max) {                        // Keine Zahl mehr im gültigen Bereich
            std::cout << "Sie sind ein Lügner!\n"; // Das Programm ist äußert entsetzt
            break;                                 // Schleife wird abgebrochen
        }
    }

    std::cout << "Die von Ihnen gemerkte Zahl ist " << zahl << "!" << std::endl; // Ausgabe der erratenen Zahl
}
Ausgabe:
Wo fängt die Zahlenreihe an?: 0
Wo hört die Zahlenreihe auf?: 100
Denken Sie an 50? <
Denken Sie an 25? >
Denken Sie an 37? <
Denken Sie an 31? >
Denken Sie an 34? <
Denken Sie an 32? >
Denken Sie an 33? =
Die von Ihnen gemerkte Zahl ist 33!

Dies ist eine Beispielausgabe, alles nach einem Fragezeichen ist eine Benutzereingabe.