C++11的基于范围的for循环
·
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; // 最后记得释放
总结
- 优先使用
const auto&避免不必要的拷贝 - 修改元素时使用
auto& - 避免在循环中修改容器结构
- 注意临时对象的生命周期
- 复杂类型使用 auto 简化代码
更多推荐



所有评论(0)