Hallo und herzlich willkommen. Bisher können wir nur auf Ereignisse und Eingaben reagieren und somit verschiedene Wege im Programmablauf einschlagen. Wir haben zwar Kreuzungen, an denen sich der Ablauf verzweigt, doch wir kommen nicht mehr an einen bestimmten Punkt im Quellcode zurück.

Vielleicht möchten wir aber nochmals an einer Gabelung stehen. Nur diesmal entscheiden wir uns für den anderen Weg.

Oder wir wollen, dass eine Operation ein weiteres Mal, aber mit dem aktuellen Wert ausgeführt wird.

Es soll also bereits ausgeführter Code wiederholt werden. Dazu musst du eine Schleife programmieren.

goto

Den rudimentärsten Rücksprung kannst du mit dem Schlüsselwort goto erzielen. Der Name sagt es schon. Mit diesem Befehl springst du an den gewünschten Punkt im Code. Dieser muss allerdings mit einem Label versehen sein, damit er eindeutig erkennbar ist.

Das folgende Beispiel in listing6 zeigt ein Ratespiel. Es wird eine Zufallszahl zwischen 0 und 10 gebildet und der Nutzer soll mit seiner Eingabe erraten, welche Zahl es ist.

War seine Zahl Antwort richtig, so wird eine neue Zufallszahl gebildet. Sonst bleibt der alte Wert bestehen.

Nach seinem Versuch wird er gefragt, ob er ein weiteres Mal raten möchte. Egal wie erfolgreich er war. Wenn der Nutzer nochmal raten möchte und dies mit y bestätigt, springt die Anweisung goto Start zum Label Start.

Das Label steht vor Abfrage nach der gesuchten Zahl. Von hier aus wird der Quellcode wiederholt ausgeführt. Und das so oft, wie die letzte Abfrage mit y beantwortet wird.

// listing1: goto instruction

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
    int guessable {rand() % 10};
    int guess {0};
    
    std::string retry {"y"};
    
    std::cout << "Guess a number between 0 and 1 " << std::endl;
    
    Start:                          // start label
    std::cout << "Your guess: ";
    std::cin >> guess;

    if(guessable > guess)
    {
        std::cout << "I'm afraid you missed! Your number is too small. " << std::endl;
    }
    else if(guessable < guess)
    {
        std::cout << "I'm afraid you missed! Your number is too large. " << std::endl;
    }
    else
    {
        std::cout << "Great! "<< guess << " is right." << std::endl;
        guessable = rand() % 10;
    }

    std::cout << "New try? (y/n)" << std::endl;
    std::cin >> retry;

    if(retry == "y")
    {
        goto Start;                 // goto instruction
    }
    else
    {
        std::cout << "See you!" << std::endl;
    }

    return 0;
}

Nun, da du dir das Beispiel angeschaut und versucht hast, es nachzuvollziehen, komme ich mit einem ABER. goto ist nicht die empfohlene Art von Programmschleifen.

Extensive Verwendung von goto Befehlen kann zu einem unvorhersehbaren Programmfluss führen. Der Programmcode wird dann nicht mehr von oben nach unten ausgeführt, sondern springt in den Zeilen hin und her. Wenn du nicht sehr achtsam bist, kann es dabei in einigen Fällen zu unvorhersehbaren Zuständen von Variablen kommen.

Im aller schlimmsten Fall führt die Programmierung mit goto zu sogenanntem Spaghetti Code. Damit ist sehr ausufernder, ineffizienter und unübersichtlicher Quellcode gemeint.

Deshalb verwende ich lieber die anderen Formen von Schleifen, die wir uns als nächstes anschauen.

Ich habe dir goto nur gezeigt, damit du weißt, was es ist, falls es dir in fremdem Quellcode über den Weg läuft.

while

Mit dem Schlüsselwort while kannst du eine Schleife ähnlich zu goto einleiten. Du kannst hiermit zwar nicht zu einem beliebigen Punkt in deinem Code springen. Dafür ist diese Schleife sehr übersichtlich und gibt dir mehr Sicherheit bei der Ausführung.

while folgt in Klammern () ein boolescher Ausdruck. Diese iterative Anweisung bestimmt, ob die Schleife durchlaufen wird oder nicht. Anschließend zu den Klammern findest du den Block für deine Anweisungen.

Solange die Auswertung des booleschen Ausdrucks true ergibt, findet ein weiterer Schleifendurchlauf des Anweisungsblocks statt. Ein Durchlaufen der Schleife bezeichnet man auch Iteration.

Lass uns doch das Beispiel aus listing1 mit einer while Schleife anstelle von goto realisieren.

// listing2: while loop

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
    int guessable {rand() % 10};
    int guess {0};
    
    std::string retry {"y"};
    std::cout << "Guess a number between 0 and 1 " << std::endl;
    
    while(retry == "y")             // while loop
    {
        std::cout << "Your guess: ";
        std::cin >> guess;

        if(guessable > guess)
        {
            std::cout << "I'm afraid you missed! Your number is too small. " << std::endl;
        }
        else if(guessable < guess)
        {
            std::cout << "I'm afraid you missed! Your number is too large. " << std::endl;
        }
        else
        {
            std::cout << "Great! "<< guess << " is right." << std::endl;
            guessable = rand() % 10;
        }

        std::cout << "New try? (y/n)" << std::endl;
        std::cin >> retry;
    }

    std::cout << "See you!" << std::endl;
    
    return 0;
}

Der größte Unterschied zur goto Variante ist die fehlende if Abfrage am Schluss. Ob der Benutzer y eingegeben hat und einen weiteren Versuch wagen möchte, muss nicht explizit ausgewerte werden.

Die Bedingung retry =="y" ist in diesem Fall die interative Anweisung. Die Schleife wird also nur so lange durchlaufen, bis die Variable einen abweichenden Wert zugewiesen bekommt.

do while

Vielleicht möchtest du ein Codesegment innerhalb einer Schleife mindestens einmal durchlaufen. Ganz unabhängig von einer Bedingung. Weitere Durchläufe finden aber nur bei erfüllter Bedingung statt.

Hier hilft dir die do ... while Schleife. Vor dem Anweisungsblock steht das Schlüsselwort do. Nach dem Block schließt die Schleife mit der Auswertung der Bedingung mit while(). Diesmal muss while() wie jede andere Anweisung mit einem Semikolon ; beendet werden.

In listing3 siehst du, wie unser Beispiel mit do ... while Schleife aussieht.

// listing3: do while

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
    int guessable {rand() % 10};
    int guess {0};

    std::string retry {"n"};
    std::cout << "Guess a number between 0 and 1 " << std::endl;

    do                                  // do
    {
        std::cout << "Your guess: ";
        std::cin >> guess;
        
        if(guessable > guess)
        {
            std::cout << "I'm afraid you missed! Your number is too small. " << std::endl;
        }
        else if(guessable < guess)
        {
            std::cout << "I'm afraid you missed! Your number is too large. " << std::endl;
        }
        else
        {
            std::cout << "Great! "<< guess << " is right." << std::endl;
            guessable = rand() % 10;
        }

        std::cout << "New try? (y/n)" << std::endl;
        std::cin >> retry;

    }while(retry == "y");               // while loop

    std::cout << "See you!" << std::endl;

    return 0;
}

Da die Variable retry erst am Ende der Schleife verglichen wird, ist ihr Wert bei der Initialisierung unerheblich. Die Schleife wird in jedem Fall durchlaufen.

Nach dem ersten Durchlauf wird die Bedingung ausgewertet. Bei true wird die Schleife nochmals durchlaufen. Ansonsten werden die folgenden Teile des Programms seriell ausgeführt.

Infinite Loop, continue und break

Hast du eine iterative Anweisung, die niemals false werden kann, findet die Schleife kein Ende. Es wird nie eine Abbruchbedingung erreicht. Ein solches Konstrukt mit while(true) trägt den Namen Infinite Loop.

Hört sich im ersten Moment nach einem Programmierfehler an. Doch es kann durchaus gewollt sein und einen Zweck erfüllen. Stell dir vor, du möchtest einen bestimmten Parameter während der gesamten Laufzeit des Programms überwachen. Du kannst den Wert in einer Schleife, die niemals endet, ständig abfragen lassen.

Doch die Infinite Loop ist nicht unkontrollierbar.

Das Schlüsselwort break haben wir schon im Zusammenhang mit switch case kennengelernt. Damit kannst du eine Schleife sofort beenden. Das Programm wird nach der Schleife fortgesetzt.

Ein weiteres Schlüsselwort, um Schleifen zu kontrollieren, ist continue. Gelangt der Programmfluss an diese Anweisung, dann springt es sofort zurück an den Anfang der Schleife. Dabei werden alle Anweisungen zwischen continue und der abschließenden Klammer ignoriert.

Unser Beispiel ist in listing4 mit einer infinite loop umgesetzt.

// listing4: infinite loop

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
    int guessable {rand() % 10};
    int guess {0};

    std::string retry {"y"};
    std::cout << "Guess a number between 0 and 1 " << std::endl;

    while(true)                     // infinite while loop
    {
        std::cout << "Your guess: ";
        std::cin >> guess;
        if(guessable > guess)
        {
            std::cout << "I'm afraid you missed! Your number is too small. " << std::endl;
            continue;
            std::cout << "You won't see me!" << std::endl;
        }
        else if(guessable < guess)
        {
            std::cout << "I'm afraid you missed! Your number is too large. " << std::endl;
            continue;
            std::cout << "You won't see me either!" << std::endl;
        }
        else
        {
            std::cout << "Great! "<< guess << " is right." << std::endl;
            guessable = rand() % 10;
        }
        
        std::cout << "New try? (y/n)" << std::endl;
        std::cin >> retry;

        if(retry != "y")
            break;                  // exit loop when expression evaluates to true
    }

    std::cout << "See you!" << std::endl;
    
    return 0;
}

for

Als nächstes schauen wir und die for Schleife an. Sie ist etwas komplexer als die bisherigen Schleifen, da du ihr eine Initialisierungsanweisung, eine Abbruchbedinung und eine Aktion nach jedem Durchlauf geben kannst. Diese werden jeweils mit einem Semikolon ‘;’ voneinander getrennt und stehen in Klammern ‘()’ direkt hinter dem Schlüsselwort ‘for’.

Häufig wird sie dazu verwendet, eine initialisierte Variable so lange zu modifizieren, bis diese einen bestimmten Wert erreicht hat.

Somit kannst du beispielsweise eine Zählervariable mit einem Anfangswert definieren, den Wert am Anfang jeder Schleife gegen eine Ausgangsbedingung prüfen und den Wert der Variablen am Ende einer Schleife ändern.

Mit einer for Schleife kannst du unser Beispiel so verändern, dass der Nutzer nur eine bestimmt Anzahl an Versuchen hat. Schafft er es nicht die richtige Zahl zu erraten, hat er leider verloren.

// listing5: for loop

#include <iostream>
#include <stdlib.h>

int main()
{
    int guessable {rand() % 10};
    int guess {0};
    int numberOfGuesses {3};
    
    std::cout << "Guess a number between 0 and 10 " << std::endl;

    for(int i = 0; i < numberOfGuesses; i++)                // for loop
    {
        std::cout << numberOfGuesses - i << " guess left" << std::endl;
        std::cout << "Your guess: ";
        std::cin >> guess;

        if(guessable > guess)
        {
            std::cout << "I'm afraid you missed! Your number is too small. " << std::endl;
        }
        else if(guessable < guess)
        {
            std::cout << "I'm afraid you missed! Your number is too large. " << std::endl;
        }
        else
        {
            std::cout << "Great! "<< guess << " is right." << std::endl;
            break;
        }
    }

    std::cout << "See you!" << std::endl;

    return 0;
}

Du kannst bei auch mehrere Variablen in einer for Schleife initialisieren. Denke aber daran, alle mit einem Komma ‘,’ voneinander zu trennen.

Das Verwenden der Initialisierung, der Bedingung und des Ausdrucks, der am Ende jedes Schleifendurchlaufs ausgewertet werden soll, ist optional. Du kannst auch eine, zwei oder alle weglassen.

Wenn du alle weglässt, dann entsteht eine Infinte Loop in Form einer for Schleife.

Nested Loops

Genauso wie if Anweisungen, kannst du auch Schleifen ineinander verschachteln. Das ist besonders hilfreich, wenn du beispielsweise Felder mit mehreren Dimensionen hast und eine Operation auf jeden Wert anwenden möchtest.

In listing6 nehmen wir zwei eindimensionale Arrays unterschiedlicher Länge und wollen jedes Element des ersten Feldes mit jedem des zweiten multiplizieren. Dazu verschachteln wir eine Schleife, die das eine Feld iteriert in eine andere, die für das andere Felder zuständig ist.

// listing6: nested loop

#include <iostream>

int main()
{
    const int array1Len = 3;
    const int array2Len = 2;

    int array1[array1Len] {26, -8, 0};
    int array2[array2Len] {19, -5};

    std::cout << "Multiplying each integer in array1 by each integer in array2:" << std::endl;

    for(int i = 0; i < array1Len; ++i)
    {
        for(int j = 0; j < array2Len; ++j)
        {
            std::cout << array1[i] << " x " << array2[j] << " = " << array1[i] * array2[j] << std::endl;
        }
    }

    return 0;
}

Zuerst werden alle Elemente von array2 mit dem ersten Element aus array1 multipliziert. Anschließend wird der ganze Vorgang für das zweite Element aus array1 wiederholt, bis jedes Element an der Reihe war.

Hast du ein mehrdimensionales Feld, dann kannst du mit Hilfe verschatelter Schleifen auf jedes Element zugreifen lassen. Eine Schleife geht alle Spalten durch, während die innere Schleife auf jedes Element der Zeile zugreift.

// listing7: multidimensional arrays

#include <iostream>

int main()
{
    const int columns = 3;
    const int rows = 2;
    
    int arrayMulti [rows][columns] { {1,2,3},{4,5,6} };
    
    for (int i = 0; i < rows; ++i)
    {
        // iterate integers in each row (columns)
        for (int j = 0; j < columns; ++j)
        {
            std::cout << "Element[" << i << "][" << j << "] = " << arrayMulti[i][j] << std::endl;
        }
    }

    return 0;
}

Das bleibt hängen

In diesem Beitrag haben wir uns die verschiedenen Arten von Schleifen angeschaut. Mit ihrer Hilfe lässt sich der Programmablauf lenken und Code effizienter schreiben. Soll eine bestimmte Anweisung mehrfach ausgeführt werden, brauchen wir diese nicht jedesmal einzutippen. In einer Schleife wird die Anweisung solange wiederholt, bis die Abbruchbedingung erfüllt wird.

Wir haben uns die grundlegenden Schleifenarten while, do while und for angeschaut, sowie die besonderen Formen der Infinite Loops und der Nested Loops.

Zudem haben wir uns kurz mit goto beschäftigt. Aber nur damit du weißt, was es ist, falls du in fremdem Quellcode darauf stößt. Mit den anderen Schleifenarten lässt sich aber jede Situation regeln und sie geben dir mehr Sicherheit sowie einen besseren Überblick auf den Programmablauf.

Jetzt haben wir genügend Werkzeug, um den Ablauf unserer Programme zu gestalten.

Ich wünsche dir maximalen Erfolg!


Quellen

  • [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
  • [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
  • [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017