关于C++11 range-for的一个陷阱
C++11提出了很多方便的特性,其中范围for(range-for)语句结合auto关键字可以方便地遍历STL容器(包括内置数组):vector vec={1,2,3,4};for(auto e:vec){cout<<e<<" ";}如果需要修改其中元素,可以声明为auto &e,如下所示:vector vec={1,2,3,4};
C++11提出了很多方便的特性,其中范围for(range-for)语句结合auto关键字可以方便地遍历STL容器(包括内置数组):
vector<int > vec={1,2,3,4};
for(auto e:vec){
cout<<e<<" ";
}
如果需要修改其中元素,可以声明为auto &e,如下所示:
vector<int > vec={1,2,3,4};
for(auto &e:vec){
if(e>2)e*=2;
cout<<e<<" ";
}
但这不是这篇博客的主题,今天记录的是自己代码中遇到的一个陷阱。剔除无关代码,问题代码如下:
vector<int> month;
int cnt=1;
month.push_back(3);
for(int i=3;i<=monthCount;i++){
for(auto &e:month){
e++;
}
for(auto e:month){
if(e>=3){
cnt++;
month.push_back(1);
}
}
}
这段代码外层for中嵌套了两个range-for,但是第一个可以用,第二个是不行的,你看出原因了么?
第二个错误在于:range-for中向遍历的vector中添加了元素。
先给出结论:不能在range-for的循环体中改变遍历的容器的大小,即不允许遍历的同时添加或删除元素!
至于原因,其实也不难理解:
我们都知道,凡是使用了迭代器的循环体中都不能向迭代器所属的容器添加元素!(C++primer,5e,P99)
因为对于某些容器,向容器中添加或删除元素会导致迭代器失效,因此后续遍历操作都是未定义的。而STL各种容器失效的时机是不同的,感兴趣的可以参考这位大神的博客:http://blog.csdn.net/yangquanhui1991/article/details/52077562,所以才有C++primer中的上述金玉良言。
再回过来看为何range-for也不可以呢?
这是因为range-for底层实现时预存了容器的end()值,而一旦遍历的时候向该容器添加或删除元素,就会使该预存的end()失效,由上述迭代器失效的问题,就不难明白:range-for的循环体中不允许对该容器添加或删除元素!
因此上面代码中的第二个range-for应该改成普通for循环,并且不能使用迭代器遍历:
vector<int> month;
int cnt=1;
month.push_back(3);
for(int i=3;i<=monthCount;i++){
for(auto &e:month){
e++;
}
for(int i=0;i<month.size();i++){//不能使用range-for或迭代器遍历!
if(month[i]>=3){
cnt++;
month.push_back(1);
}
}
}
其实range-for的这个陷阱在C++ primer第五版里已经做出了警示,当时看书时也做了记号,只是这种问题真的只有自己犯过一两次错误后才能记得!而且这种错误一旦发生,很难发现错误根源,编译期无错误无警告,而且运行时不同编译器执行的结果可能不一样!因为迭代器失效后再执行后续循环将是未定义的行为,所以C++primer建议如果使用迭代器遍历,每次在插入或删除元素后都应该重新定位迭代器,对于这点在写程序时一定要有一个清醒的认识。
更多推荐
所有评论(0)