遍历容器是种广泛的需求,在C++11之前,有些库提供了遍历容器内所有元素的封装方法,比如Boost中的BOOST_FOREACH,Qt中的foreach关键字等等,甚至C++自己也提供了一个std::for_each算法。
C++11基于范围的for循环
-
std::vector<int> coll = { 1, 2, 3};
-
for( int i : coll ) {
-
std::cout << i << std::endl;
-
}
以上代码以值拷贝方式访问到容器coll中的每个元素,这里可以使用 auto
来自动推导容器内元素的类型:
-
std::vector<int> coll = { 1, 2, 3};
-
for( auto i : coll ) {
-
std::cout << i << std::endl;
-
}
如果需要修改容器内元素的内容,则需要声明引用类型 int& i
或 auto& i
。所以当容器内元素类型是复杂数据类型时,为运行效率考虑计,一般推荐引用或常量引用方式访问:
-
std::vector<std::string> coll = { "element1", "element2", "element3"};
-
for (const auto& s : coll) {
-
std::cout << s << std::endl;
-
}
std::map
是按 std::pair
迭代的,所以要这样遍历 std::map
:
-
std::map<std::string, std::string> mm;
-
for ( const auto& m : mm ) {
-
std::cout << m.first << " < " << m.second << ">" << std::endl;
-
}
一般而言,如下一组基于范围的for循环:
-
for ( for-range-declaration : expression )
-
statement
等价于如下一组老式的for循环:
-
{
-
auto && __range = ( expression );
-
for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) {
-
for-range-declaration = *__begin;
-
statement
-
}
-
}
其中 __range
, __begin
和 __end
仅用于说明, __RangeT
是 expression
的类型, begin-expr
和 end-expr
则依据以下规则决定:
-
如果
__RangeT
是数组类型,则begin-expr
和end-expr
分别等于__range
和__range +__bound
,相应的__bound
是数组边界。因此如此__RangeT
是不知大小的数组,或者不完整类型(有声明没定义)的数组,那么程序就不合法。 -
如果
__RangeT
类型拥有begin()
和end()
成员函数,则begin-expr
和end-expr
分别等于__range.begin()
和__range.end()
。 -
否则,
begin-expr
和end-expr
分别等于begin(__range)
和end(__range)
,使用参数依赖查找算法进行查找,其实基本上就是std::begin()
和std::end()
。 -
__begin
和__end
具有相同的类型,在C++17中放宽了这个限制。
目前C++标准库中所有容器, std::string
和数组都能用这种基于范围的for循环遍历,如果想要让自己的数据结构也支持这种语法,需要满足以下要求:
-
能对此自定义数据结构类型调用
begin
和end
方法,无论是成员函数或者独立函数都可以,要能返回迭代器类型。 -
返回的迭代器类型必须支持
operator*
方法,operator!=
方法和前缀形式的operator++
方法,同样无论是成员函数或独立函数都可以。
C++17的改进
C++11引入的基于范围的for循环要求 begin
和 end
(起始值和结尾值)具有相同的类型,这对于大多数情况来说并没有什么问题,比如在遍历STL容器时,总是能返回相同类型的 begin
和 end
。
但是有人觉得这个规范过于受限,于是C++17放开了这个限制,将原来的等价代码修改如下:
-
{
-
auto && __range = for-range-initializer;
-
auto __begin = begin-expr;
-
auto __end = end-expr;
-
for ( ; __begin != __end; ++__begin ) {
-
for-range-declaration = *__begin;
-
statement
-
}
-
}
与C++11中的相比,唯一的不同就是 __begin
和 __end
可以具有不同类型了,只要它们两个支持通过 operator!=
比较即可。