Namespaces
Variants

Range-based for loop (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
for
range- for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

在某个范围内执行 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&& /* 范围 */ = 范围初始化器  ;
for (auto /* 起始 */ = /* 起始表达式 */ , /* 结束 */ = /* 结束表达式 */ ;
/* 起始 */ != /* 结束 */ ; ++ /* 起始 */ )
{
项声明 = * /* 起始 */ ;
语句
}

}

(C++17 前)

{

auto&& /* 范围 */ = 范围初始化器  ;
auto /* 起始 */ = /* 起始表达式 */ ;
auto /* 结束 */ = /* 结束表达式 */ ;
for ( ; /* 起始 */ != /* 结束 */ ; ++ /* 起始 */ )
{
项声明 = * /* 起始 */ ;
语句
}

}

(C++17 起)
(C++20 前)

{

初始化语句
auto&& /* 范围 */ = 范围初始化器  ;
auto /* 起始 */ = /* 起始表达式 */ ;
auto /* 结束 */ = /* 结束表达式 */ ;
for ( ; /* 起始 */ != /* 结束 */ ; ++ /* 起始 */ )
{
项声明 = * /* 起始 */ ;
语句
}

}

(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; // 错误:重复声明

临时范围初始化器

如果 range-initializer 返回一个临时对象,其生命周期将延长至循环结束,这是通过绑定到转发引用 /* range */ 实现的。

所有位于 范围初始化器 内的临时对象生命周期均不会延长 ,除非它们原本就会在 范围初始化器 结束时被销毁 (C++23 起)

// 如果 foo() 通过值返回
for (auto& x : foo().items()) { /* ... */ } // C++23 之前是未定义行为

此问题可通过使用 初始化语句 解决:

for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
(since 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
}
(since C++23)

注释

如果 range-initializer 是一个 大括号包围的初始化列表 /* range */ 会被推导为 std::initializer_list 的引用。

在泛型代码中使用推导至转发引用是安全的,实际上更可取, for ( auto && var : sequence )

当范围类型拥有名为“ begin ”和“ end ”的成员时,将采用成员解释方式。此规则与成员是类型、数据成员、函数还是枚举项无关,也与其可访问性无关。因此,即使存在命名空间作用域的“ begin ”/“ end ”函数,类似 class meow { enum { begin = 1 , end = 2 } ; /* 类其余部分 */ } ; 这样的类也无法用于基于范围的 for 循环。

虽然在 item-declaration 中声明的变量通常会在 statement 中使用,但并非必须如此。

自 C++17 起, /* begin-expr */ /* end-expr */ 的类型不必相同,实际上 /* end-expr */ 的类型甚至不必是迭代器:只需能够与迭代器进行不等性比较即可。这使得通过谓词(例如“迭代器指向空字符时”)界定范围成为可能。

(since 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)) { /* ... */ }
(since C++17)
功能测试宏 标准 功能特性
__cpp_range_based_for 200907L (C++11) 基于范围的 for 循环
201603L (C++17) 具有不同 begin / end 类型的基于范围 for 循环
202211L (C++23) 范围初始化器 中所有临时对象的生存期扩展

关键词

for

示例

#include <iostream>
#include <vector>
int main()
{
    std::vector<int> v = {0, 1, 2, 3, 4, 5};
    for (const int& i : v) // 通过常量引用访问
        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)
    // 类型定义声明作为初始化语句(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 init-statement 中引入的名称可以被重新声明 此情况下程序格式错误
CWG 2825 C++11 range-initializer 为花括号包围的初始化列表,
将查找非成员“ begin ”和“ end
此情况下将查找成员“ begin
和“ end
P0962R1 C++11 只要存在成员“ begin
或“ end ”就采用成员解释
仅当两者同时存在时才采用

参见

范围 内的元素应用一元 函数对象
(函数模板)