Zum Inhalt springen

Kontrollfluss & Schleifen

Meistere alle Kontrollflusskonstrukte von C++: if/else, switch, sämtliche Schleifenformen, die Range-basierte for-Schleife, die Initialisierer aus C++17 und Structured Bindings.

Kontrollfluss in C++

Kontrollflussanweisungen legen fest, in welcher Reihenfolge dein Programm abläuft. C++ bietet dafür einen reichhaltigen Satz an Konstrukten, der weit über die von C geerbten Grundlagen hinausgeht. Modernes C++ (ab C++17) ergänzt Features wie if mit Initialisierer und constexpr if, die den Kontrollfluss ausdrucksstärker und sicherer machen.

Ein zentraler Grundsatz: Halte den Gültigkeitsbereich von Variablen klein. Jede Variable sollte so nah wie möglich an ihrer ersten Verwendung und in einem möglichst engen Gültigkeitsbereich deklariert werden. Features wie if mit Initialisierer aus C++17 helfen dir dabei.

if/else und switch

Die Anweisungen if und switch sind die wichtigsten Verzweigungsmechanismen. C++17 ergänzt beide um Initialisiererklauseln:

control_flow.cppc++23
#include <iostream>
#include <map>
#include <string>
#include <optional>

std::optional<int> find_value(const std::string& key) {
    std::map<std::string, int> data = {{"alpha", 1}, {"beta", 2}, {"gamma", 3}};
    if (auto it = data.find(key); it != data.end()) {  // C++17 if with initializer
        return it->second;
    }
    return std::nullopt;
}

int main() {
    // C++17: if with initializer — the variable's scope is limited to the if/else
    if (auto val = find_value("beta"); val.has_value()) {
        std::cout << "Found: " << *val << '\n';
    } else {
        std::cout << "Not found\n";
    }
    // 'val' is no longer in scope here — cleaner!

    // switch with C++17 initializer
    enum class Color { Red, Green, Blue };
    Color c = Color::Green;

    switch (auto name = "unknown"; c) {  // C++17 switch with initializer
        case Color::Red:   name = "red";   std::cout << name << '\n'; break;
        case Color::Green: name = "green"; std::cout << name << '\n'; break;
        case Color::Blue:  name = "blue";  std::cout << name << '\n'; break;
    }

    // [[fallthrough]] attribute (C++17) — document intentional fallthrough
    int priority = 2;
    switch (priority) {
        case 1:
            std::cout << "Critical: ";
            [[fallthrough]];       // Tells the compiler this is intentional
        case 2:
            std::cout << "Important: ";
            [[fallthrough]];
        case 3:
            std::cout << "Handle it\n";
            break;
        default:
            std::cout << "Unknown priority\n";
    }
}
Ausgabe

Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.

Schleifen: for, while, do-while und die Range-basierte for-Schleife

C++ bietet mehrere Schleifenformen. Die Range-basierte for-Schleife (C++11) ist beim Durchlaufen von Containern vorzuziehen, weil sie Off-by-one-Fehler ausschließt und klarer zu lesen ist:

loops.cppc++23
#include <iostream>
#include <vector>
#include <string>
#include <map>

int main() {
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    // Traditional for loop — use when you need the index
    for (std::size_t i = 0; i < names.size(); ++i) {
        std::cout << i << ": " << names[i] << '\n';
    }

    // Range-based for loop (C++11) — preferred for iteration
    for (const auto& name : names) {        // const auto& avoids copying
        std::cout << name << '\n';
    }

    // Range-based for with structured bindings (C++17)
    std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
    for (const auto& [name, score] : scores) {   // Destructure the pair
        std::cout << name << " scored " << score << '\n';
    }

    // C++20: range-based for with init-statement
    for (auto v = std::vector{1, 2, 3, 4, 5}; auto elem : v) {
        std::cout << elem << ' ';
    }
    std::cout << '\n';

    // while loop — use when the number of iterations is unknown
    int n = 1;
    while (n <= 1024) {
        std::cout << n << ' ';
        n *= 2;
    }
    std::cout << '\n';

    // do-while — body executes at least once
    int input;
    do {
        std::cout << "Enter a positive number: ";
        input = 42;  // Simulated input for example
    } while (input <= 0);
}
Ausgabe

Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.

Structured Bindings (C++17)

Structured Bindings (C++17) erlauben es dir, ein Objekt in seine Bestandteile zu zerlegen. Sie funktionieren mit Arrays, Structs mit ausschließlich öffentlichen Membern, std::pair, std::tuple und jedem Typ, der das Protokoll für Structured Bindings unterstützt.

Die Syntax auto [a, b] = expr; erzeugt benannte Verweise auf die Bestandteile von expr. Du kannst die Qualifizierer const und & ergänzen: const auto& [a, b] = expr; erzeugt konstante Referenzen, ohne zu kopieren.

Besonders stark sind Structured Bindings beim Durchlaufen einer std::map (wo sie std::pair zerlegen), bei Funktionen, die std::pair oder std::tuple zurückgeben, und beim Rückgabewert der Methode insert assoziativer Container.

Best Practice

Verwende break, um eine Schleife vorzeitig zu verlassen, sobald du gefunden hast, was du brauchst. Verwende continue, um den Rest des aktuellen Durchlaufs zu überspringen und zum nächsten zu gehen. Mit Augenmaß eingesetzt, machen beide Schleifen verständlicher.

Bevorzuge die Range-basierte for-Schleife gegenüber der indexbasierten, wenn du den Index nicht brauchst. Sie schließt ganze Fehlerklassen aus: Off-by-one-Fehler, die Verwendung der falschen Variablen als Index und Warnungen zu Vergleichen zwischen signed und unsigned.

Brauchst du den Index doch, nimm als Typ der Schleifenvariablen am besten std::size_t, passend zum Rückgabetyp von .size(), oder verwende std::ssize() (C++20), das eine vorzeichenbehaftete Größe liefert.

Vermeide tief verschachtelte Schleifen — wenn du drei oder mehr Schleifen ineinander verschachtelst, lagere die inneren in Funktionen aus. Das verbessert Lesbarkeit und Testbarkeit.

  • Bevorzuge Range-basierte for-Schleifen gegenüber indexbasierten, wenn du den Index nicht brauchst
  • Verwende const auto& in der Range-basierten for-Schleife, um unnötige Kopien zu vermeiden
  • Verwende Structured Bindings, um Paare und Tupel in Schleifenvariablen zu zerlegen
  • Lagere tief verschachtelte Schleifen der Klarheit halber in benannte Funktionen aus

constexpr if (C++17): Verzweigen zur Kompilierzeit

if constexpr wertet die Bedingung zur Kompilierzeit aus und verwirft den Zweig, der nicht genommen wird. Das ist in der Template-Programmierung unverzichtbar, wo einzelne Zweige für manche Typen gar nicht gültig sein müssen:

constexpr_if.cppc++23
#include <iostream>
#include <type_traits>
#include <string>

template <typename T>
void describe(T value) {
    std::cout << "Value: " << value;

    if constexpr (std::is_integral_v<T>) {
        // This branch only compiles when T is an integer type
        std::cout << " (integer, " << (value % 2 == 0 ? "even" : "odd") << ")";
    } else if constexpr (std::is_floating_point_v<T>) {
        // This branch only compiles when T is a floating-point type
        std::cout << " (floating-point)";
    } else {
        std::cout << " (other type)";
    }
    std::cout << '\n';
}

int main() {
    describe(42);           // Value: 42 (integer, even)
    describe(3.14);         // Value: 3.14 (floating-point)
    describe("hello");      // Value: hello (other type)
}
Ausgabe

Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.

Wichtigste Erkenntnisse
  • if/switch mit Initialisierer (C++17) beschränken den Gültigkeitsbereich der Variablen auf die Anweisung — nutze sie für sauberen Code
  • Range-basierte for-Schleifen schließen Off-by-one-Fehler und Probleme beim Vergleich von signed und unsigned aus
  • Structured Bindings (C++17) zerlegen Paare, Tupel und Structs in benannte Variablen
  • Verwende [[fallthrough]], um ein absichtliches Durchfallen in einem switch-Zweig zu dokumentieren
  • if constexpr (C++17) wertet Bedingungen zur Kompilierzeit aus — der verworfene Zweig wird nicht kompiliert
  • Halte den Gültigkeitsbereich von Variablen klein: deklariere sie so nah wie möglich an ihrer ersten Verwendung

Quiz — Teste dein Wissen

(15 XP)

1. Was ist der Vorteil von `if` mit Initialisierer aus C++17: `if (auto x = f(); x > 0)`?

2. Was bewirkt `for (const auto& [key, val] : my_map)`?

3. Was geschieht mit dem verworfenen Zweig einer `if constexpr`-Anweisung?