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

来自cppreference.com
< cpp‎ | language


 
 
 
 

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

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

目录

[编辑]语法

属性 (可选)for (初始化语句 (可选)项声明:范围初始化器)语句
属性 - 任意数量的属性
初始化语句 - (C++20 起) 以下之一:
(C++23 起)

注意,所有初始化语句 必然以分号结尾。因此它经常被非正式地描述为后随分号的表达式或声明。

项声明 - 范围中每一项的声明
范围初始化器 - 表达式花括号包围的初始化器列表
语句 - 任意语句(典型情况下是复合语句)

[编辑]解释

上述语法产生的代码等价于下列代码,但范围初始化器 中的临时量会进行生存期扩展(见下文(C++23 起)(以 /* */ 包围的变量和表达式仅用于阐述)。

{

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 起)

要迭代的序列或范围通过对范围初始化器 求值以确定。依次对序列的每个元素进行解引用,并赋值给具有项声明 中所给定的类型和名字的变量。

项声明 可以是以下之一:

  • 结构化绑定声明
(C++17 起)

仅用于阐述的表达式 /* 首表达式 *//* 尾表达式 */ 定义如下:

  • 如果 /* range */ 的类型是到数组类型 R 的引用,那么:
  • 如果 RN 个元素,那么 /* 首表达式 *//* range *//* 尾表达式 *//* range */+ N
  • 如果 R 是边界未知或元素类型不完整的数组,那么程序非良构。
  • 如果 /* range */ 的类型是到类类型 C 的引用,并且在 C 的作用域中对名字 “begin” 和 “end” 的查找都能各自找到至少一条声明,那么 /* 首表达式 *//* range */.begin()/* 尾表达式 *//* range */.end()
  • 否则 /* 首表达式 */begin(/* range */)/* 尾表达式 */end(/* range */),其中 “begin” 和 “end” 会通过实参依赖查找进行查找(不进行非实参依赖查找)。

如果需要在语句 中结束循环,那么可以使用 break 语句作为终止语句。

如果需要在语句 中结束当前迭代,那么可以使用 continue 语句作为快捷方式。

如果从初始化语句 中引入的名字在语句 的最外层块被重声明,那么程序非良构:

for(int i :{1, 2, 3})int i =1;// 错误:重声明

[编辑]临时范围初始化器

如果范围初始化器 返回了临时量,那么它的生存期会延续到循环结尾,如绑定到转发引用 /* range */ 所示,但要注意范围初始化器 中任何临时量生存期都不会延长,除非它们本会在范围初始化器 末尾被销毁(C++23 起)

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

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

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;}// 总是返回悬垂引用 T g();   void foo(){for(auto e : f1(g())){}// OK: g() 的返回值的生存期被延长for(auto e : f2(g())){}// UB: f2 的值形参的生存期过早结束}
(C++23 起)

[编辑]注解

如果范围初始化器 是花括号包围的初始化器列表,那么 /* 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 起)
功能特性测试宏 标准功能特性
__cpp_range_based_for200907L(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};//创建一个vector   for(constint& i : v)// 以 const 引用访问std::cout<< i <<' ';std::cout<<'\n';   for(auto i : v)// 以值访问,i 的类型是 intstd::cout<< i <<' ';std::cout<<'\n';   for(auto&& i : v)// 以转发引用访问,i 的类型是 int&std::cout<< i <<' ';std::cout<<'\n';   constauto& 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++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1442 C++11 未指明查找非成员 “begin” 和 “end” 时是否包含通常的无限定查找 不包含
CWG 2220 C++11 可以重声明从初始化语句 中引入的名字 此时程序非良构
CWG 2825 C++11 如果范围初始化器 是花括号包围的初始化器列表,那么会查找非成员 “begin” 和 “end 此时会查找成员 “begin” 和 “end
P0962R1 C++11 只要 “begin” 和 “end” 之一存在就使用成员解释方案 只有在两者都存在时才使用

[编辑]参阅

应用一元函数对象范围中元素
(函数模板)[编辑]
close