Modern C++特性:基于范围的for循环
遍历容器是种广泛的需求,在C++11之前,有些库提供了遍历容器内所有元素的封装方法,比如 Boost
中的 BOOST_FOREACH
, Qt
中的 foreach
关键字等等,甚至C++自己也提供了一个 std::for_each
算法。
C++11基于范围的for循环
std::vector coll = { 1, 2, 3}; for( int i : coll ) { std::cout << i << std::endl; }
以上代码以值拷贝方式访问到容器coll中的每个元素,这里可以使用 auto
来自动推导容器内元素的类型:
std::vector coll = { 1, 2, 3}; for( auto i : coll ) { std::cout << i << std::endl; }
如果需要修改容器内元素的内容,则需要声明引用类型 int& i
或 auto& i
。所以当容器内元素类型是复杂数据类型时,为运行效率考虑计,一般推荐引用或常量引用方式访问:
std::vector coll = { "element1", "element2", "element3"}; for (const auto& s : coll) { std::cout << s << std::endl; }
std::map
是按 std::pair
迭代的,所以要这样遍历 std::map
:
std::map 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!=
比较即可。
这为类作者、库作者提供了更多的灵活性。
觉得本文不错的话,分享一下给小伙伴吧~