命名空间
变体
操作

基于范围的 for 循环 (自 C++11 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++17* 中弃用)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

在一个范围内执行 for 循环。

用作传统 for 循环在值范围(例如容器中的所有元素)上操作的更具可读性的等效替代方案。

目录

[编辑] 语法

attr (可选) for ( init-statement (可选) item-declaration : range-initializer ) statement
attr - 任意数量的 属性
init-statement - (自 C++20 起) 以下之一
(自 C++23 起)

注意,任何 init-statement 必须以分号结尾。这就是为什么它经常被非正式地描述为表达式或声明后跟分号的原因。

item-declaration - 每个范围项的声明
range-initializer - 表达式花括号括起来的初始化列表
statement - 任何 语句 (通常是复合语句)

[编辑] 解释

上面的语法产生的代码等效于以下代码 除了 range-initializer 的临时对象的生命周期扩展 (参见 下方)(自 C++23 起) (用 /* */ 包裹的变量和表达式仅用于说明)

{

auto&& /* range */ = range-initializer ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(直到 C++17)

{

auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(自 C++17 起)
(直到 C++20)

{

init-statement
auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(自 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 中,它们在被调用者中而不是在调用者中被销毁),但这仅对于无论如何都有错误的函数而言才是一个问题

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” 的成员,则使用成员解释。 无论成员是类型、数据成员、函数还是枚举器,也无论其可访问性如何,都这样做。 因此,即使存在命名空间作用域的 “begin”/“end” 函数,像 class meow { enum { begin = 1, end = 2 }; /* rest of class */ }; 这样的类也不能与基于范围的 for 循环一起使用。

虽然在 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 起)
特性测试宏 Std 特性
__cpp_range_based_for 200907L (C++11) 基于范围的 for 循环
201603L (C++17) 具有 不同 begin/end 类型的基于范围的 for 循环
202211L (C++23) 所有临时对象在 range-initializer 中的生命周期延长

[编辑] 关键词

for

[编辑] 示例

#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++ 标准。

DR 应用于 已发布行为 正确行为
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”,则使用成员解释
仅当两者都存在时才使用

[编辑] 参见

将一元函数对象应用于来自范围的元素
(函数模板) [编辑]