range based for 可以看做 boost 的 FOR_EACH 或者 java 的增强版本的 for, 但它不同于stl 的for_each算法
.
方式对比
旧方式
原来的循环:
1 | int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
以及迭代器版本:
1 | std::vector<int> vec {1,2,3,4,5,6,7,8,9,10}; |
不管上面哪一种方法,都必须明确的确定for循环开头以及结尾条件
range for
range for循环(或者成为for each)基本用法: 遍历字符串,数组,map,vector等容器, 语法如下:
1 | for (decl : container) { |
例如, 用于容器
1 | string s("hello,world"); |
当然也可以这么用:
1 | for(int i: {1,2,3,4,5}){ |
迭代器(推导)问题
貌似使用range_for时, 根本没有涉及到迭代器
:
但是编译器底层还是把它转换成我们一般
使用迭代器的版本
, 只是不需要我们手动那么写了.
range for 里面, 直接推导出, value_type, 作为auto &c
部分, 而不去涉及迭代器.
写map
就明白了:
1 | std::map<string, int> map{{"a", 1}, {"b",2}}; |
也就是说, auto &val
其实是 std::pair<string, value>
;
对比for_each算法
range for 不同于 for_each
for_each用于逐个遍历容器元素,它对迭代器区间[first,last)所指的每一个元素,执行由单参数函数对象f所定义的操作
模板函数for_each,大概是这样的:
1 | template<typename InputIterator, typename Function> |
可用于遍历, 使用案例: (当然你用functor或者funtional对象等代替函数也是可以的)
1 | void print(int& elem) { |
注意事项
因range for还是会被编译器编译成带有迭代器版本的for, 所以迭代器可能存在的问题, 它也会有.
下面说说注意事项:
用于关联容器
请不要对关联式容器, 比如map, set等, 通过迭代器改变容器元素内容
他们的实现借助红黑树, 维持有序的, 所以应该是先删除, 再添加, 最好你只用range for来进行只读操作,for(const auto &c:s)
.
举个具体的例子:
1 | std::map<string, int> map{ { "a", 1 }, { "b", 2 }, { "c", 3 } }; |
或者std::set
, 你去修改, 直接编译器就报错了, 即便你用的是它的引用.
只读情况(元素只读), 请写
const auto&
元素兼容问题
另外还有一点注意, range for
左边的元素应该和容器里面的元素, 类型兼容或者一致
否则编译器尝试的转换可能就会失败, 比如下面的就会进行隐式转换, 导致报错:
1 | class C |
迭代器失效问题
下面说说改变容器的情况,(不同于改变容器元素)
不要在遍历途中, 修改容器属性(比如增加元素
例如:
1 | vector<int> vec = { 1, 2, 3, 4, 5, 6 }; |
因为底层还是会被编译器编译成迭代器版本, 改变了容器, 意味着迭代器失效
, 出任何莫名奇妙的bug都不奇怪.
关于迭代器失效问题, 可以参考我的文章 《STL迭代器失效问题》, 当然也可以参考 《C++ primer 5e》
函数返回值问题
在range for 容器的位置, 放了函数表达式? 没问题的, 只要函数的返回值是容器类型, 一样好使.
例如下面的代码, 正常工作, 没有任何问题;
1 | set<int> ss = { 1, 2, 3, 4, 5, 6 }; |
高级话题
如果你要自定义一个类(相当于自定义一个容器
), 然后可以使用 range_for来遍历这个类; 这个时候, 你就会深刻体会到 for each需要容器支持
(设计容器的时候, 同时设计迭代器
).
稍稍总结一下, 你知道要在容器&迭代器中做哪些工作: (最好你理解迭代器设计模式
)
- 定义
自定义类的迭代器
//可以简单到只是一个裸指针, 保存容器索引即可 - 该类型拥有begin() 和 end() 成员方法(或者重载全局的begin() 和 end() 函数也可以),返回值为迭代器
- 自定义迭代器的
!= 比较操作
- 自定义迭代器的
++ 前置自增操作
,显然该操作要是迭代器对象指向该容器的下一个元素 - 自定义迭代器
* 解引用操作
,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错
遍历终止点, 遍历范围,都是依靠begin
, end
函数的. 所以:
偷懒的话, 只要设计一下, begin, end, 解引用以及前置自增即可; 但一定要容器和迭代器配合
给一个简单的示例代码:
1 | template <typename T> |
输出为:
1 | 1 2 3 4 5 6 7 8 9 |
迭代器, 操作容器, 其余范围问题, 都交给了 range for 本身.