跳至内容

控制流:if、循环与现代模式

掌握 C++ 的控制流 ── 从经典的 if/else 和循环到现代的基于范围的 for、结构化绑定、if constexpr 以及 C++17 的带初始化器 if。

条件语句

C++ 提供了 if/else、switch,以及条件(三元)运算符用于分支。C++17 在 if 和 switch 语句中添加了初始化器 ── 限制变量作用域到条件块中。

循环:for、while 与基于范围的 for

C++ 支持经典的 for、while、do-while 循环以及现代的基于范围的 for 循环。基于范围的版本更安全、更简洁,适用于任何可迭代的容器。

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++17)

结构化绑定让你将一个复合对象(pair、tuple、struct)拆解为命名变量。它在基于范围的 for 循环中与 std::map 配合使用时特别强大。

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);
}
输出

点击“运行”即可编译并运行这个示例。

if constexpr(C++17)

if constexpr 在编译时求值条件。未选中的分支被完全丢弃 ── 它甚至不需要对当前模板参数有效。这对于泛型编程至关重要。

常见陷阱

忘记 break 会导致执行「贯穿」到下一个 case 标签 ── 这通常是一个错误。C++17 添加了 [[fallthrough]] 属性来记录有意的贯穿,消除编译器警告。

另一个陷阱:在 case 标签内部的变量声明。如果你在一个 case 中声明变量,它对后续的 case 是可见的但未初始化。如果你在 case 中需要局部变量,始终使用花括号(case 1: { int x = ...; break; })。

  • 忘记 break 会导致贯穿到下一个 case ── 使用 [[fallthrough]] 记录有意的贯穿
  • 在 case 标签中声明变量时使用花括号以限制作用域
  • 基于范围的 for 遍历临时对象时要小心 ── 确保它在整个循环期间存活
  • 在基于范围的 for 中使用 const auto& 以避免不必要的拷贝
最佳实践

1. 优先使用基于范围的 for 而非经典 for 循环 ── 更安全、更清晰、不易出现差一错误。
2. 使用 const auto& 在基于范围的 for 循环中 ── 避免复制并防止意外修改。
3. 使用 C++17 带初始化器的 if ── 最小化变量作用域(if (auto it = m.find(k); it != m.end()))。
4. 优先使用 if constexpr 而非 SFINAE ── 对于模板中的编译时分支更简洁。
5. 尽早 return 以减少嵌套 ── 深层嵌套的 if/else 链很难阅读。
6. 在 switch 中使用 [[fallthrough]] ── 记录意图,消除警告。

关键要点
  • 基于范围的 for(for (auto& x : v))比经典循环更安全、更简洁
  • C++17 结构化绑定(auto [key, val] = pair)拆解复合对象
  • 带初始化器的 if(if (auto x = f(); x > 0))限制变量到 if/else 块
  • switch 默认会贯穿 ── 始终使用 break 或 [[fallthrough]]
  • if constexpr(C++17)在编译时求值条件 ── 丢弃的分支不会被编译
  • 最小化变量作用域:尽可能将变量声明在靠近首次使用的地方

测验 — 检验你的知识

(15 经验值)

1. C++17 的带初始化器 `if` 的优势是什么:`if (auto x = f(); x > 0)`?

2. `for (const auto& [key, val] : my_map)` 做了什么?

3. `if constexpr` 语句的被丢弃分支会发生什么?