基于范围的 for 循环 (C++11 起)
在一个范围上执行 for 循环。
用作对范围中的各个值(如容器中的所有元素)进行操作的传统 for 循环的更加可读的等价版本。
语法
属性 (可选) for ( 初始化语句 (可选) 项声明 : 范围初始化器 ) 语句
|
|||||||||
| 属性 | - | 任意数量的属性 | ||
| 初始化语句 | - | (C++20 起) 以下之一:
注意,所有初始化语句 必然以分号结尾。因此它经常被非正式地描述为后随分号的表达式或声明。 | ||
| 项声明 | - | 范围中每一项的声明 | ||
| 范围初始化器 | - | 表达式或花括号包围的初始化器列表 | ||
| 语句 | - | 任意语句(典型情况下是复合语句) |
解释
上述语法产生的代码等价于下列代码,但范围初始化器 中的临时量会进行生存期扩展(见下文)(C++23 起)(以 /* */ 包围的变量和表达式仅用于阐述)。
|
|
(C++17 前) |
|
|
(C++17 起) (C++20 前) |
|
|
(C++20 起) |
要迭代的序列或范围通过对范围初始化器 求值以确定。依次对序列的每个元素进行解引用,并赋值给具有项声明 中所给定的类型和名字的变量。
项声明 可以是以下之一:
- 具有以下限制的简单声明:
- 它只有一个声明符。
|
(C++17 起) |
仅用于阐述的表达式 /* 首表达式 */ 和 /* 尾表达式 */ 定义如下:
- 如果
/* range */的类型是到数组类型R的引用,那么:
- 如果
R有N个元素,那么/* 首表达式 */是/* range */,/* 尾表达式 */是/* range */ + N。 - 如果
R是边界未知或元素类型不完整的数组,那么程序非良构。
- 如果
- 如果
/* range */的类型是到类类型C的引用,并且在C的作用域中对名字 “begin” 和 “end” 的查找都能各自找到至少一条声明,那么/* 首表达式 */是/* range */.begin(),/* 尾表达式 */是/* range */.end()。 - 否则
/* 首表达式 */是begin(/* range */),/* 尾表达式 */是end(/* range */),其中 “begin” 和 “end” 会通过实参依赖查找进行查找(不进行非实参依赖查找)。
如果需要在语句 中结束循环,那么可以使用 break 语句作为终止语句。
如果需要在语句 中结束当前迭代,那么可以使用 continue 语句作为快捷方式。
如果从初始化语句 中引入的名字在语句 的最外层块被重声明,那么程序非良构:
for (int i : {1, 2, 3})
int i = 1; // 错误:重声明
临时范围初始化器
如果范围初始化器 返回了临时量,那么它的生存期会延续到循环结尾,如绑定到转发引用 /* range */ 所示,但要注意范围初始化器 中任何临时量生存期都不会延长,除非它们本会在范围初始化器 末尾被销毁(C++23 起)。
// 如果 foo() 按值返回
for (auto& x : foo().items()) { /* .. */ } // C++23 前为行为未定义
|
此问题可用初始化语句 变通解决: for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
|
(C++20 起) |
|
注意,即便在 C++23 中,中间函数的非引用形参也不会延长生存期(因为某些 ABI 中它们在被调用方而非调用方中销毁),但这点仅对于本就有缺陷的函数才会造成问题: using T = std::list<int>;
const T& f1(const T& t) { return t; }
const T& f2(T t) { return t; } // 总是返回悬垂引用
T g();
void foo()
{
for (auto e : f1(g())) {} // OK: g() 的返回值的生存期被延长
for (auto e : f2(g())) {} // UB: f2 的值形参的生存期过早结束
}
|
(C++23 起) |
注解
如果范围初始化器 是花括号包围的初始化器列表,那么 /* 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: struct cow_string { /* ... */ }; // 写时复制的字符串
cow_string str = /* ... */;
// for(auto x : str) { /* ... */ } // 可能会导致深层复制
for(auto x : std::as_const(str)) { /* ... */ }
|
(C++17 起) |
| 功能特性测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_range_based_for |
200907L |
(C++11) | 基于范围的 for 循环
|
201603L |
(C++17) | 具有不同的 begin/end 类型的基于范围的 for 循环
| |
202211L |
(C++23) | 对范围初始化器 中所有临时对象的生存期扩展 |
关键词
示例
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {0, 1, 2, 3, 4, 5};//创建一个vector
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';
const auto& cv = v;
for (auto&& i : cv) // 以转发引用访问,i 的类型是 const 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 ([[maybe_unused]] int n : a)
std::cout << 1 << ' '; // 不必使用循环变量
std::cout << '\n';
for (auto n = v.size(); auto i : v) // 初始化语句(C++20)
std::cout << --n + i << ' ';
std::cout << '\n';
for (typedef decltype(v)::value_type elem_t; elem_t i : v)
// typedef 声明作为初始化语句(C++20)
std::cout << i << ' ';
std::cout << '\n';
for (using elem_t = decltype(v)::value_type; elem_t i : v)
// 别名声明作为初始化语句,同上(C++23)
std::cout << i << ' ';
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
0 1 2 3 4 5
1 1 1 1 1 1
5 5 5 5 5 5
0 1 2 3 4 5
0 1 2 3 4 5
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
| 缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1442 | C++11 | 未指明查找非成员 “begin” 和 “end” 时是否包含通常的无限定查找
|
不包含 |
| CWG 2220 | C++11 | 可以重声明从初始化语句 中引入的名字 | 此时程序非良构 |
| CWG 2825 | C++11 | 如果范围初始化器 是花括号包围的初始化器列表,那么会查找非成员 “begin” 和 “end”
|
此时会查找成员 “begin” 和 “end”
|
| P0962R1 | C++11 | 只要 “begin” 和 “end” 之一存在就使用成员解释方案
|
只有在两者都存在时才使用 |
参阅
| 应用一元函数对象到范围中元素 (函数模板) |