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建议如果使用迭代器遍历,每次在插入或删除元素后都应该重新定位迭代器,对于这点在写程序时一定要有一个清醒的认识。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐