Перейти к содержимому

Контроль потока и петли

Управляйте всеми конструкциями потока управления C++, включая if/else, переключатель, все формы цикла, основанные на диапазоне для инициализаторов C++17 и структурированные связывания.

Контрольный поток в C++

Высказывания потока управления определяют порядок выполнения вашей программы. C++ предоставляет богатый набор конструкций потока управления, которые выходят далеко за рамки основ, унаследованных от C. Современный C++ (C++17 и далее) добавляет такие функции, как if с инициализаторами и constexpr if, которые делают поток управления более выразительным и безопасным.

Ключевой принцип: свести к минимуму объем переменных . Каждая переменная должна быть объявлена как можно ближе к ее первому использованию и в самом узком объеме. Функции C++17, такие как if с инициализаторами, помогают вам следовать этому принципу.

if/else и коммутировать

Заявления if и switch являются основными механизмами ветвления. C++17 добавляет инициализаторы:

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";
    }
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Петли: для, в то время, в то время и на основе диапазона для

C++ предлагает несколько форм цикла. Цикл на основе диапазона для (C++11) предпочтительнее при итерации над контейнерами, поскольку он устраняет ошибки один за другим и яснее:

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);
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Структурированные связи (C++17)

Структурные привязки (C++17) позволяют разложить объект на составные части. Они работают с массивами, структурами со всеми публичными участниками, std::pair, std::tuple и любым типом, поддерживающим протокол структурированных связываний.

Синтаксис auto [a, b] = expr; создает именованные ссылки на компоненты expr. Вы можете добавить квалификаторы const и &: const auto& [a, b] = expr; создает ссылки const без копирования.

Структурированные связывания особенно эффективны при итерации std::map (разложение std::pair), возвратах функций, использующих std::pair или std::tuple, и возвратном значении метода insert на ассоциативных контейнерах.

Лучшая практика

Используйте break, чтобы выйти из цикла раньше, когда вы нашли то, что вам нужно. Используйте continue, чтобы пропустить остальную часть текущей итерации и перейти к следующей. Оба делают петли более понятными при разумном использовании.

Предпочитаете индекс на основе диапазона для по сравнению с индексом, когда вам не нужен индекс. Он устраняет целые классы ошибок: ошибки один за другим, используя неправильную переменную в качестве индекса и подписанные / неподписанные предупреждения о сравнении.

Когда вам нужен индекс, рассмотрите возможность использования std::size_t в качестве типа переменной цикла для соответствия типу возврата .size() или используйте std::ssize() (C++20), который возвращает подписанный размер.

Избегайте глубоко вложенных петель — если вы обнаружите, что вложены три или более петель, извлеките внутренние петли в функции. Это улучшает читаемость и проверяемость.

  • Предпочитают циклы на основе диапазона, когда индекс не нужен
  • Используйте Const Auto & в диапазоне, чтобы избежать ненужных копий
  • Используйте структурированные связывания для разложения пар и кортежей в петлевых переменных
  • Извлеките глубоко вложенные петли в названные функции для ясности

constexpr if (C++17): ветвление компиляции

if constexpr оценивает состояние во время компиляции и отбрасывает незанятое отделение. Это важно в программировании шаблонов, где различные ветви могут быть недействительны для всех типов:

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)
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Ключевые выводы
  • C++17, если/переключается с инициализаторами, ограничивает область переменной в заявлении - используйте их для поддержания чистоты кода
  • Контуры, основанные на диапазоне, устраняют ошибки по отдельности и проблемы сравнения с подписью / подписью
  • Структурированные связывания (C++17) разлагают пары, кортежи и структуры в названные переменные
  • Использование [[fallthrough]] для документирования преднамеренного переключения случайного провала
  • если constexpr (C++17) оценивает условия во время компиляции — выброшенная ветвь не компилируется
  • Минимизировать объем переменных: объявить переменные как можно ближе к первому использованию

Тест — проверьте свои знания

(15 XP)

1. В чем преимущество C++17 `if` с инициализатором: `if (auto x = f(); x > 0)`?

2. Чем занимается `for (const auto& [key, val] : my_map)`?

3. Что происходит с выброшенным ответвлением заявления `if constexpr`?