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

来自cppreference.com
< cpp‎ | language

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

用作对范围中的各个值(如容器中的所有元素)进行操作的传统 for 循环的更加可读的等价版本。

语法

属性(可选) for ( 初始化语句(可选)范围变量声明 : 范围表达式 ) 循环语句
属性 - 任意数量的属性
初始化语句 - (C++20 起) 以下之一:
(C++23 起)
注意,所有 初始化语句 必然以分号 ; 结尾,因此它经常被非正式地描述为后随分号的表达式或声明。
范围变量声明 - 一个具名变量的声明,它的类型是由 范围表达式 所表示的序列的元素的类型,或该类型的引用。通常用 auto 说明符进行自动类型推导。
范围表达式 - 可以表示一个合适的序列(数组,或定义了 beginend 成员函数或自由函数的对象,见下文)的任意表达式,或一个花括号初始化器列表
循环语句 - 任意语句,通常是一条复合语句,它是循环体

范围变量声明 可以是结构化绑定声明

for (auto&& [first,second] : mymap) {
    // 使用 first 和 second
}
(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),通过实参依赖查找进行查找(不进行非实参依赖查找)。

正如传统循环一样,break 语句可用于提早退出循环,且 continue 语句能用于以下个元素重新开始循环。

临时范围表达式

如果 范围表达式 返回临时量,那么它的生存期延续到循环结尾,如绑定到转发引用 __range 所示,但要注意 范围表达式 中任何临时量生存期都会延长。

for (auto& x : foo().items()) { /* .. */ } // 如果 foo() 返回右值则为未定义行为

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

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

struct cow_string { /* ... */ }; // 写时复制的字符串
cow_string str = /* ... */;
 
// for(auto x : str) { /* ... */ } // 可能会导致深层复制
 
for(auto x : std::as_const(str)) { /* ... */ }
(C++17 起)

关键词

for

示例

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

DR 应用于 出版时的行为 正确行为
P0962R1 C++11 只要 begin 和 end 之一存在就使用成员解释方案 只有在两者都存在时才使用

参阅

应用函数到范围中的元素
(函数模板)