基于范围的 for
循环 (C++11 起)
对范围进行 for 循环。
用作传统 for 循环的更具可读性的替代品,用于遍历值范围,例如容器中的所有元素。
目录 |
[编辑] 语法
attr (可选) for ( init-statement (可选) item-declaration : range-initializer ) statement |
|||||||||
属性 | - | 任意数量的属性 | ||
init-statement | - | (C++20 起) 之一
注意,任何 init-statement 都必须以分号结尾。这就是为什么它通常被非正式地描述为表达式或声明后跟分号的原因。 | ||
item-declaration | - | 每个范围项的声明 | ||
range-initializer | - | 表达式 或 花括号初始化列表 | ||
语句 | - | 任何语句 (通常是复合语句) |
[编辑] 解释
上述语法生成的代码等价于以下代码 除了 range-initializer 的临时对象的生命周期扩展 (参见下文)(C++23 起) (包裹在 /* */ 中的变量和表达式仅用于说明)
|
(C++17 前) |
|
(C++17 起) (C++20 前) |
|
(C++20 起) |
range-initializer 被求值以初始化要迭代的序列或范围。序列的每个元素依次被解引用并用于初始化具有 item-declaration 中给定的类型和名称的变量。
item-declaration 可以是以下之一
仅用于说明的表达式 /* begin-expr */ 和 /* end-expr */ 定义如下
- 如果 /* range */ 的类型是对数组类型
R
的引用
- 如果
R
具有界限 N,则 /* begin-expr */ 为 /* range */,/* end-expr */ 为 /* range */ + N。 - 如果
R
是未知界限数组或不完整类型数组,则程序格式错误。
- 如果
- 如果 /* range */ 的类型是对类类型
C
的引用,并且在C
的作用域中搜索名称“begin
”和“end
”都至少找到一个声明,则 /* begin-expr */ 为 /* range */.begin(),/* end-expr */ 为 /* range */.end()。 - 否则,/* begin-expr */ 为 begin(/* range */),/* end-expr */ 为 end(/* range */),其中“
begin
”和“end
”通过实参依赖查找(不执行非 ADL 查找)找到。
如果循环需要在 statement 内终止,可以使用 break 语句作为终止语句。
如果当前迭代需要在 statement 内终止,可以使用 continue 语句作为快捷方式。
如果在 init-statement 中引入的名称在 statement 的最外层块中被重新声明,则程序格式错误
for (int i : {1, 2, 3}) int i = 1; // error: redeclaration
[编辑] 临时范围初始化器
如果 range-initializer 返回一个临时对象,其生命周期会延长到循环结束,如绑定到转发引用 /* range */ 所示。
range-initializer 中所有临时对象的生命周期不会延长,除非它们本来会在 range-initializer 结束时销毁(C++23 起)。
// if foo() returns by value for (auto& x : foo().items()) { /* ... */ } // until C++23 undefined behavior
此问题可通过使用 init-statement 解决 for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(C++20 起) |
请注意,即使在 C++23 中,中间函数调用的非引用参数也不会获得生命周期扩展(因为在某些 ABI 中,它们在被调用方而不是调用方中销毁),但这仅对本身存在 bug 的函数而言是个问题。 using T = std::list<int>; const T& f1(const T& t) { return t; } const T& f2(T t) { return t; } // always returns a dangling reference T g(); void foo() { for (auto e : f1(g())) {} // OK: lifetime of return value of g() extended for (auto e : f2(g())) {} // UB: lifetime of f2's value parameter ends early } |
(C++23 起) |
[编辑] 注意
如果 range-initializer 是花括号初始化列表,则 /* range */ 会被推导为对 std::initializer_list 的引用。
在通用代码中,使用转发引用推导是安全的,实际上是更推荐的,例如 for (auto&& var : sequence)。
如果范围类型具有名为“begin
”和“end
”的成员,则使用成员解释。无论该成员是类型、数据成员、函数还是枚举器,也无论其可访问性如何。因此,像 class meow { enum { begin = 1, end = 2 }; /* rest of class */ }; 这样的类不能与基于范围的 for 循环一起使用,即使存在命名空间作用域的“begin
”/“end
”函数。
虽然在 item-declaration 中声明的变量通常在 statement 中使用,但这并非必需。
截至 C++17,/* begin-expr */ 和 /* end-expr */ 的类型不必相同,实际上 /* end-expr */ 的类型不必是迭代器:它只需要能够与其中一个进行不等比较。这使得可以通过谓词(例如“迭代器指向空字符”)来界定范围。 |
(C++17 起) |
当与具有写时复制语义的(非 const)对象一起使用时,基于范围的 for 循环可能会通过(隐式)调用非 const begin()
成员函数触发深层复制。
如果这是不希望的(例如,因为循环实际上并未修改对象),可以通过使用 std::as_const 来避免 struct cow_string { /* ... */ }; // a copy-on-write string cow_string str = /* ... */; // for (auto x : str) { /* ... */ } // may cause deep copy for (auto x : std::as_const(str)) { /* ... */ } |
(C++17 起) |
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_range_based_for |
200907L |
(C++11) | 基于范围的 for 循环 |
201603L |
(C++17) | 基于范围的 for 循环,带有不同 begin /end 类型 | |
202211L |
(C++23) | 延长 range-initializer 中所有临时对象的生命周期 |
[编辑] 关键词
[编辑] 示例
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // access by const reference std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // access by value, the type of i is int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // access by forwarding reference, the type of i is int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // access by f-d reference, the type of i is const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a // braced-enclosed initializer list std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // the initializer may be an array std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // the loop variable need not be used std::cout << '\n'; for (auto n = v.size(); auto i : v) // the init-statement (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // typedef declaration as init-statement (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // alias declaration as init-statement (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 | 在 init-statement 中引入的名称可以被重新声明 | 在这种情况下程序格式错误 |
CWG 2825 | C++11 | 如果 range-initializer 是一个花括号初始化列表, 将查找非成员“ begin ”和“end ” |
在这种情况下将查找成员“begin ”和“ end ” |
P0962R1 | C++11 | 如果存在成员“begin ”和“end ”,则使用成员解释成员“ begin ”和“end ”都存在时才使用 |
仅当两者都存在时才使用 |
[编辑] 另请参阅
对范围中的元素应用一元函数对象 (函数模板) |