引言

在C++98/03时代,遍历一个容器,标准写法是这样的:

std::vector<int> vec {1, 2, 3, 4, 5};

// 方式一:下标索引
for (size_t i = 0; i < vec.size(); ++i) {
    std::cout << vec[i] << " ";
}

// 方式二:迭代器
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";
}

上述代码展示了下标索引受限于容器类型且容易越界,迭代器写法则充斥着重复的begin()/end()
我们真正想要的,只是遍历集合中的每一个元素,至于怎么取开头、怎么判断结尾、怎么移动到下一个——这些底层细节理应交给编译器。
于是,有了基于范围的for循环。

一、基本语法与使用

for ( 声明 : 表达式 ) {
    循环体
}

1.1 基础示例

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers {10, 20, 30};
    
    // 值拷贝
    for (int n : numbers) {
        std::cout << n << " ";  // 10 20 30
    }

    // 原生数组同样支持
    int arr[] = {100, 200, 300};
    for (int n : arr) {
        std::cout << n << " ";  // 100 200 300
    }

    // 初始化列表
    for (char c : {'a', 'b', 'c'}) {
        std::cout << c << " ";   // a b c
    }
}

1.2 配合auto使用

std::vector<std::string> words {"Hello", "World", "C++"};

// 使用 auto 自动推导类型
for (auto word : words) {
    std::cout << word << " ";
}

二、选择正确的类型

声明方式 语义 适用场景
for (auto x : range) 值拷贝。每个元素都会被复制一份 元素是基本类型(如 int、char),或者确实需要修改副本而不影响原容器时
for (const auto x : range) 只读副本。拷贝后不可修改 基本类型且只需读取,或不想意外修改副本
for (auto& x : range) 左值引用。直接引用原元素,可修改 需要修改容器内元素时使用
for (const auto& x : range) 常量左值引用。只读访问,避免拷贝 绝大多数只读遍历的首选。尤其是元素类型较大(如 std::string、自定义类)时,既高效又安全
for (auto&& x : range) 万能引用。保留值类别,可绑定到左值或右值 编写泛型代码时,或者处理代理类(如 std::vector)时
场景 推荐写法
只读遍历,元素类型较大(std::string、容器) for (const auto& x : range)
只读遍历,元素类型是基本类型(int、char) for (auto x : range) 或 for (const auto x : range)
需要修改容器内元素 for (auto& x : range)
编写泛型代码,或处理 vector 等代理类 for (auto&& x : range)
需要索引或额外状态(C++20) for (init; auto& x : range)
循环中需要修改容器结构(增删元素) 不要用范围 for,改用传统迭代器或下标循环