C++11 起可以用范围 for 遍历容器、数组或初始化列表,不必手写下标。这种写法在底层就是对 begin 与 end 之间的迭代器做循环,因此本节会同时给出范围 for 和迭代器两种写法,方便对照。迭代器的类别与失效规则见后面的迭代器章节。
基本语法
形式为 for (声明 : 范围表达式) 语句。范围表达式常为数组、std::vector、std::string 等;每次迭代将其中的元素交给循环变量。
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5};
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}输出:
3 1 4 1 5 循环变量 x 为各元素的拷贝,修改 x 不影响 v。对 int 等小类型开销可忽略;对大型对象应使用引用以避免拷贝。
引用与 const
std::vector<int> v = {1, 2, 3};
for (int& x : v) {
x *= 2; // 修改容器内元素,v 变为 2, 4, 6
}
const std::vector<int> frozen = {1, 2};
for (const int& x : frozen) {
std::cout << x; // 只读,不可写 x
}只读遍历大对象时,const auto& 可避免拷贝且禁止修改,适用于 std::string 或自定义结构体(auto 详见auto 与 decltype)。frozen 为 const 时,循环变量不能写成非 const 引用。
| 循环变量形式 | 拷贝 | 可修改元素 |
|---|---|---|
T x | 是 | 否(改的是副本) |
T& x | 否 | 是(非 const 范围) |
const T& x | 否 | 否 |
内置数组与花括号列表
int arr[] = {9, 8, 7};
for (int n : arr) {
std::cout << n << " ";
}
for (int x : {1, 2, 3}) {
std::cout << x;
}第一段遍历数组 arr:数组名在范围 for 里仍表示整块数组,因此能走完 9、8、7。若手里只有指向首元素的指针、没有长度,就不能对这个指针单独写范围 for。
第二段没有事先声明数组,{1, 2, 3} 会生成一份临时的初始化列表,类型是 std::initializer_list<int>,范围 for 按这几个值走一遍。这种方式只适合当场列出少量数字,循环结束后这份列表就消失了。
与迭代器的关系
迭代器表示容器里的当前位置:v.begin() 指向第一个元素,v.end() 指向最后一个元素的后面(这个位置没有元素,不能写成 *v.end())。++it 移到下一个元素,*it 取出当前值。范围 for 不会让你手写这些步骤,编译器会按同一套规则展开,因此下面两段对这个例子打印的都是 1020:
std::vector<int> v = {10, 20};
for (int x : v) {
std::cout << x;
}
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it;
}只是依次访问每个元素时,范围 for 更短。需要在循环里比较位置、插入或删除时,就要自己拿着迭代器写。数组没有 begin 成员,范围 for 会用指向首元素和尾后位置的指针来充当迭代器。更完整的说明见迭代器章节。
修改范围时的注意
在范围 for 遍历过程中对同一容器执行 push_back 等可能使迭代器失效,导致未定义行为。若需在循环中增删元素,应使用下标循环、迭代器算法,或先收集修改再统一应用。仅通过引用改当前元素的值,一般不涉及插入删除,不受此限。
本节说明了范围 for 的写法、引用选择与迭代器对应关系。下一章从指针开始,讨论地址、解引用与空指针。