在一个范围上执行 for
循环。
用作对范围中的各个值(如容器中的所有元素)进行操作的传统 for 循环的更加可读的等价版本。
语法
属性(可选) for ( 范围声明 : 范围表达式 ) 循环语句 | (C++20 前) | |
属性(可选) for ( 初始化语句(可选)范围声明 : 范围表达式 ) 循环语句 | (C++20 起) | |
属性 | - | 任何数量的属性 |
初始化语句(C++20) | - | 以下之一: - 表达式语句(可为空语句“ ; ”)- 简单声明,典型地为带初始化器的变量声明,但它可以声明任意多个变量,或为结构化绑定声明 - 注意,任何 初始化语句 必然以分号 ; 结尾,此乃它经常被非正式地描述为后随分号的表达式或声明的原因。 |
范围声明 | - | 一个具名变量的声明,其类型是由 范围表达式 所表示的序列的元素的类型,或该类型的引用。通常用 auto 说明符进行自动类型推导。 |
范围表达式 | - | 任何可以表示一个合适的序列(数组,或定义了 begin 和 end 成员函数或自由函数的对象,见下文)的表达式,或一个花括号初始化器列表。 |
循环语句 | - | 任何语句,常为一条复合语句,它是循环体 |
范围声明 可以是结构化绑定声明
| (C++17 起) |
解释
上述语法产生的代码等价于下列代码(__range
、__begin
和 __end
仅用于阐释):
{ - auto && range = 范围表达式 ; - for (auto begin = 首表达式, end = 尾表达式 ; begin != end; ++begin) { - 范围声明 = begin; - 循环语句 - } } | (C++17 前) |
{ - auto && range = 范围表达式 ; - auto begin = 首表达式 ; - auto end = 尾表达式 ; - for ( ; begin != end; ++__begin) { - 范围声明 = begin; - 循环语句 - } } | (C++17 起)(C++20 前) |
{ - 初始化语句 - auto && range = 范围表达式 ; - auto begin = 首表达式 ; - auto end = 尾表达式 ; - for ( ; begin != end; ++begin) { - 范围声明 = * begin; - 循环语句 - } } | (C++20 起) |
对 范围表达式 求值以确定要迭代的序列或范围。依次对序列的每个元素进行解引用,并赋值给具有 范围声明 中所给定的类型和名字的变量。
首表达式
与 尾表达式
定义如下:
- 若 范围表达式 是数组类型的表达式,则
首表达式
为 range 而尾表达式
为 (range + bound),其中bound
是数组的元素数目(若数组大小未知或拥有不完整类型,则程序非良构) - 若 范围表达式 是同时拥有名为 begin 以及名为 end 的成员的类类型
C
的表达式(不管这些成员的类型或可见性),则首表达式
为 range.begin() 而尾表达式
为 range.end(); - 否则,
首表达式
为 begin(range) 而尾表达式
为 end(range),通过实参依赖查找进行查找(不进行非 ADL 查找)。
正如传统循环一样,break 语句可用于提早退出循环,而 continue 语句能用于以下个元素重新开始循环。
临时范围表达式
若 范围表达式 返回临时量,则其生存期被延续到循环结尾,如绑定到转发引用 __range
所示,但要注意 范围表达式 中任何临时量生存期都不被延长。
- for (auto& x : foo().items()) { /* .. */ } // 若 foo() 返回右值则为未定义行为
此问题可用 初始化语句 变通解决:
| (C++20 起) |
注解
若初始化器(范围表达式)是花括号初始化器列表,则 __range
被推导为 std::initializer_list<>&&。
在泛型代码中,使用推导的转发引用,如 for (auto&& var : sequence),是安全且受推荐的做法。
若范围类型拥有名为 begin
的成员和名为 end
的成员,则使用成员解释方案。其中不顾成员是类型、数据成员、函数还是枚举项,且不顾其可访问性。从而如 class meow { enum { begin = 1, end = 2}; / 类的剩余部分 / }; 的类不能用于基于范围的 for 循环,即使存在命名空间作用域的 begin/end 函数也是如此。
虽然通常在 循环语句 中使用声明于 范围声明 的变量,但并不要求这么做。
从 C++17 开始,首表达式 和 尾表达式 的类型不必相同,而且实际上 尾表达式 的类型不必是迭代器:它只需能与一个迭代器比较是否不等即可。这使得以一个谓词(例如“迭代器指向空字符”)对范围进行分界成为可能。 | (C++17 起) |
当基于范围的 for 循环被用于一个具有写时复制语义的(非 const)对象时,它可能会通过(隐式)调用非 const 的 begin()
成员函数触发深层复制。
如果想要避免这种行为(比如循环实际上不会修改这个对象),可以使用 std::as_const:
| (C++17 起) |
关键词
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
P0962R1 | C++11 | 若 begin 和 end 之一存在就使用成员解释方案 | 仅若两者都存在才使用 |
示例
运行此代码
- #include <iostream>
- #include <vector>
- int main() {
- std::vector<int> v = {0, 1, 2, 3, 4, 5};
- for (const int& i : v) // 以 const 引用访问
- std::cout << i << ' ';
- std::cout << '\n';
- for (auto i : v) // 以值访问,i 的类型是 int
- std::cout << i << ' ';
- std::cout << '\n';
- for (auto& i : v) // 以引用访问,i 的类型是 int&
- std::cout << i << ' ';
- std::cout << '\n';
- for (int n : {0, 1, 2, 3, 4, 5}) // 初始化器可以是花括号初始化器列表
- std::cout << n << ' ';
- std::cout << '\n';
- int a[] = {0, 1, 2, 3, 4, 5};
- for (int n : a) // 初始化器可以是数组
- std::cout << n << ' ';
- std::cout << '\n';
- for (int n : a)
- std::cout << 1 << ' '; // 不必使用循环变量
- std::cout << '\n';
- }
输出:
- 0 1 2 3 4 5
- 0 1 2 3 4 5
- 0 1 2 3 4 5
- 0 1 2 3 4 5
- 0 1 2 3 4 5
- 1 1 1 1 1 1