Контроль потока и петли
Управляйте всеми конструкциями потока управления C++, включая if/else, переключатель, все формы цикла, основанные на диапазоне для инициализаторов C++17 и структурированные связывания.
Контрольный поток в C++
Высказывания потока управления определяют порядок выполнения вашей программы. C++ предоставляет богатый набор конструкций потока управления, которые выходят далеко за рамки основ, унаследованных от C. Современный C++ (C++17 и далее) добавляет такие функции, как if с инициализаторами и constexpr if, которые делают поток управления более выразительным и безопасным.
Ключевой принцип: свести к минимуму объем переменных . Каждая переменная должна быть объявлена как можно ближе к ее первому использованию и в самом узком объеме. Функции C++17, такие как if с инициализаторами, помогают вам следовать этому принципу.
if/else и коммутировать
Заявления if и switch являются основными механизмами ветвления. C++17 добавляет инициализаторы:
#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) предпочтительнее при итерации над контейнерами, поскольку он устраняет ошибки один за другим и яснее:
#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 оценивает состояние во время компиляции и отбрасывает незанятое отделение. Это важно в программировании шаблонов, где различные ветви могут быть недействительны для всех типов:
#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`?