C++11引入的基于范围的for循环最大程度上简化了遍历各个容器的代码

以下是它的语法、原理、使用场景以及注意事项

基本语法

for (元素声明 : 遍历对象) { 循环体语句 }

其中“元素声明”是定义循环中单个元素的访问方式;“遍历对象”是要所迭代的序列(就比如数组)

元素声明
// 值拷贝(创建副本)
for (auto elem : container) {}

// 只读引用(没有拷贝,推荐)
for (const auto& elem : container) {}

// 可以修改引用
for (auto& elem : container) {}

// 移动语义(C++11)
// 万能引用
for (auto&& elem : container) {}  
遍历对象
// 容器对象
std::vector<int> vec {1, 2, 5, 9};
for (auto x : vec) {}

// 数组
int arr[5];
for (auto x : arr) {}

// 初始化列表
for (auto x : {1, 2, 3, 4}) {}

// 函数返回的临时容器
std::vector<int> getVector() {}
for (auto x : getVector()) {}
原理

编译器会把范围for循环自动地“翻译”成迭代器循环

for(auto a : container) {
    循环体代码;
}

// 编译器
{
    // 1、获取所要遍历的范围
    auto&& __range = container;
    // 2、获取迭代器边界(优先调用begin/end,没有则找全局)
    auto __begin = begin(__range);
    auto __end = end(__range);
    // 3、传统的迭代器循环
    for(; __begin != __end; ++__begin) {
        // 4、解引用迭代器
        auto a = *__begin;
        // 5、执行循环体代码
        循环体代码;
    }
}
使用场景
遍历标准库容器
// vector 最常用场景
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& name : names) {
    std::cout << name << std::endl;
}

// map 注意得到的是 pair
std::map<int, std::string> id_and_name = {{1, "Alice"}, {2, "Bob"}};
for (const auto& pair : id_and_name) {  
    std::cout << pair.first << ": " << pair.second << std::endl;
}

// set 
std::set<int> unique_numbers = {1, 2, 3, 2, 1};
for (const auto& num : unique_numbers) {
    std::cout << num << " ";  // 输出: 1 2 3
}
修改容器元素
std::vector<int> numbers = {1, 2, 3, 4, 5};

// 修改元素值
for (auto& num : numbers) {
    num *= 2;  // 直接修改原容器中的元素
}

// 输出: 2 4 6 8 10
for (const auto& num : numbers) {
    std::cout << num << " ";
}
处理复杂数据
struct Person {
    std::string name;
    int age;
};

std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}};

// 修改对象成员
for (auto& person : people) {
    // 给每个人年龄+1
    person.age += 1;  
}

// 只读访问
for (const auto& person : people) {
    std::cout << person.name << " is " << person.age << " years old" << std::endl;
}
注意事项
迭代器失效
std::vector<int> vec = {1, 2, 3, 4, 5};

// 危险!可能导致未定义行为
for (auto& elem : vec) {
    if (elem == 3) {
        vec.push_back(6);  // 可能引起重新分配,迭代器失效
        // vec.erase(std::find(vec.begin(), vec.end(), 2));  // 同样危险
    }
}

// 安全做法:先收集要操作的元素,循环结束后再处理
std::vector<int> to_remove;
for (const auto& elem : vec) {
    if (should_remove(elem)) {
        to_remove.push_back(elem);
    }
}
// 循环结束后再删除
性能高低
std::vector<std::string> strings = {"hello", "world", "test"};

// 性能差:每次循环都要拷贝字符串
for (std::string str : strings) {  // 拷贝构造!
    // ...
}

// 性能好:使用const 引用,避免拷贝, 并且安全
for (const std::string& str : strings) {  // 无拷贝
    // ...
}

引用类型的选择
// 正确选择引用类型的指导原则:
// 1. 只读访问 -> const auto&
for (const auto& elem : container) {}

// 2. 需要修改元素 -> auto&
for (auto& elem : container) {}

// 3. 简单内置类型 -> auto (值传递也可以)
// 但对于复杂或者大型对象时,还是推荐使用引用
for (auto elem : container) {}  // 如果 elem 是 int, double 等

// 4. 通用模板代码 -> auto&& (完美转发场景)
template<typename Container>
void process(Container&& c) {
    for (auto&& elem : std::forward<Container>(c)) {}
}
范围生命周期
// 危险:悬空引用
std::vector<int>* get_vector_ptr();  // 返回动态分配的vector

for (int elem : *get_vector_ptr()) {  // 临时指针被销毁!
    // elem 可能引用已释放的内存
}

// 安全做法:延长生命周期
auto vec_ptr = get_vector_ptr();      // 保存指针
for (int elem : *vec_ptr) {           // 安全使用
    // ...
}
delete vec_ptr;                       // 最后记得释放
总结
  1. 优先使用 const auto& 避免不必要的拷贝
  2. 修改元素时使用 auto&
  3. 避免在循环中修改容器结构
  4. 注意临时对象的生命周期
  5. 复杂类型使用 auto 简化代码
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐