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

来自cppreference.com
< cpp‎ | language

在范围上执行 for 循环。

在一个范围的值,例如一个容器中所有元素上操作时,用作传统 for 循环的更加可读的等价物。

目录

[编辑] 语法

属性(可选) for ( 范围声明 : 范围表达式 ) 循环语句
属性(可选) for ( 初始化语句(可选)范围声明 : 范围表达式 ) 循环语句 (C++20 起)
属性 - 任何数量的属性
范围声明 - 一个具名变量的声明,其类型是由 范围表达式 所表示的序列的元素的类型,或该类型的引用。通常用 auto 指定符进行自动类型推导。
初始化语句(C++20) - 以下之一:
注意,任何 初始化语句 必须以分号 ; 结尾,此乃它经常被非正式地描述为后随分号的表达式或声明的原因。
范围表达式 - 任何可以表示一个合适的序列(数组或定义了 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 起)
{
初始化语句
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) ,通过参数依赖查找寻找(不进行非 ADL 查找)。

范围表达式 返回临时量,则其生存期被延续到循环结尾,如同绑定到右值引用 __range 所示,但要注意 范围表达式 中任何临时量生存期都被延长。

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

for (auto& x : foo().items()) { /* .. */ } // 若 foo() 返回右值则为未定义行为
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
(C++20 起)

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

属性 表示可选数量的属性

[编辑] 注意

若初始化器( 范围表达式 )是花括号初始化器列表,则 __range 被推导为 std::initializer_list<>&&

在泛型代码中,使用推导进行引用转发, for (auto&& var : sequence) ,是安全且受推荐的做法。

任何名为 beginend 成员的存在,无关乎它是类型、数据成员、函数或枚举项,且无关乎其可访问性,将导致 __range.begin()__range.end() 被各自用作 首表达式尾表达式 。因此,基于范围的 for 循环不能用于含有名为 beginend 的成员类型或枚举项的类,即使提供了无关命名空间的相应函数。

虽然声明于 范围声明 的变量通常在 循环语句 中使用,但不强制这么做。

从 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)) { /* ... */ }

[编辑] 关键词

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';
 
    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 (int n : a)  
        std::cout << 1 << ' '; // 循环变量不必使用
    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
1 1 1 1 1 1

[编辑] 参阅

将一个函数应用于某一范围的元素
(函数模板) [编辑]