关于C++11 range-for的一个陷阱

C/C++ 专栏收录该内容
59 篇文章 0 订阅

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

  • 1
    点赞
  • 2
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值