Přeskočit na obsah

Control Flow & Loops

Master all C++ control flow constructs including if/else, switch, all loop forms, range-based for, C++17 initializers, and structured bindings.

Control Flow in C++

Control flow statements determine the order in which your program executes. C++ provides a rich set of control flow constructs that go well beyond the basics inherited from C. Modern C++ (C++17 and beyond) adds features likeifwith initializers andconstexpr ifthat make control flow more expressive and safer.

A key principle: minimize the scope of variables. Every variable should be declared as close to its first use as possible, and in the narrowest scope possible. C++17 features likeifwith initializers help you follow this principle.

if/ else a switch

Příkazyifaswitchjsou primárními mechanismy větvení. C++17 přidává klauzule inicializátoru do obou:

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";
    }
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Smyčky: for, while, do-while a na základě rozsahu for

C++ nabízí více forem smyček. Smyčka na základě rozsahu pro (C++11) je preferována při iteraci přes kontejnery, protože eliminuje chyby typu off-by-one a je jasnější:

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);
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Strukturované vazby (C++17)

Strukturované vazby (C++17) umožňují rozložit objekt na jeho základní části. Pracují s poli, strukturami se všemi veřejnými členy,std::pair,std::tuplea jakýmkoli typem, který podporuje protokol strukturovaných vazeb.

Syntaxeauto [a, b] = expr;vytváří pojmenované odkazy na komponentyexpr. Můžete přidat kvalifikátoryconsta&:const auto& [a, b] = expr;vytváří const reference bez kopírování.

Strukturované vazby jsou obzvláště výkonné s iteracístd::map(rozkládajícístd::pair), návraty funkcí, které používajístd::pairnebostd::tuple, a návratovou hodnotou metodyinsertna asociativních kontejnerech.

Osvědčený postup

Použijtebreakk předčasnému ukončení smyčky, když najdete, co potřebujete. Pomocícontinuepřeskočte zbytek aktuální iterace a přejděte na další. Oba usnadňují pochopení smyček, když se používají uvážlivě.

Pokud index nepotřebujete, upřednostněte na základě rozsahu pro před indexem. Eliminuje celé třídy chyb: chyby off-by-one, použití nesprávné proměnné jako indexu a upozornění na porovnání podepsané/nepodepsané.

Pokud index potřebujete, zvažte použitístd::size_tjako typu proměnné smyčky, aby odpovídal návratovému typu.size(), nebo použijtestd::ssize()(C++20), který vrací velikost se znaménkem.

Vyhněte se hluboce vnořeným smyčkám – pokud zjistíte, že vnořujete tři nebo více smyček, extrahujte vnitřní smyčky do funkcí. To zlepšuje čitelnost a testovatelnost.

  • Pokud index není potřeba, upřednostněte cykly for založené na rozsahu před cykly založené na indexu
  • Chcete-li předejít zbytečným kopiím, použijte const auto& v rozsahu
  • Use structured bindings to decompose pairs and tuples in loop variables
  • Extract deeply nested loops into named functions for clarity

constexpr if (C++17): Compile-Time Branching

if constexpr evaluates the condition at compile time and discards the branch that is not taken. This is essential in template programming where different branches may not even be valid for all types:

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)
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Klíčové poznatky
  • C++17 if/switch with initializers limit variable scope to the statement — use them to keep code clean
  • Range-based for loops eliminate off-by-one errors and signed/unsigned comparison issues
  • Structured bindings (C++17) decompose pairs, tuples, and structs into named variables
  • Use [[fallthrough]] to document intentional switch case fallthrough
  • if constexpr (C++17) evaluates conditions at compile time — the discarded branch is not compiled
  • Minimalizovat rozsah proměnných: deklarovat proměnné co nejblíže prvnímu použití

Kvíz — Otestujte své znalosti

(15 XP)

1. Jaká je výhoda`if`s inicializátorem C++17:`if (auto x = f(); x > 0)`?

2. Co dělá`for (const auto& [key, val] : my_map)`?

3. Co se stane s vyřazenou větví příkazu`if constexpr`?