STL迭代器失效的几种情况与应对方法

前言

前几天面试过程中问到了这个问题,但回答的很模糊,如今得闲正好梳理总结一下。废话不多说直接进入正题。

一. STL标准库迭代器失效的原因

  • 使用erase方法删除元素导致的迭代器失效
  • 使用insert方法插入元素导致的迭代器失效
  • 容器扩容导致的迭代器失效

二. 失效原因

谈到迭代器失效就不得不提到stl中的几种容器类别,STL的容器分为

  • 序列式容器,如vector,deque(容器中的元素按照插入顺序存储,可以随机访问每个元素)
  • 链表类容器,如list(容器中的元素通过指针链接在一起,不一定存储在一块连续的内存空间中)
  • 关联类容器,如map, set,multimap,multiset(容器中的元素根据键值排序,并且不允许重复的键值存在。)

1. 序列式容器失效原因

(1) erase方法导致it迭代器从当前位置到容器末尾所有元素迭代器全部失效
int main() 
{
    vector<int> a{1, 2, 3, 4, 5};
    for(vector<int>::iterator it=a.begin();it!=a.end();it++)
    {
        //去除容器内的所有奇数
        if(*it%2==1)
            a.erase(it);
    }
    for (auto elem : a) 
        cout << elem << endl;
    return 0;
}

输出:报错
这是因为当我们删除it这个迭代器所指向的元素之后,it就变成了野指针。当it成为野指针之后我们对其进行++操作就会发生未定义行为,非法访问野指针,但是这个操作在release模式下不会报错,这是因为release模式下会对程序进行优化有概率不报错。那么其中还有一种重要的点就是:此类容器发生迭代器失效之后,从当前位置到容器末尾所有元素迭代器全部失效,因为erase之后后续所有元素都会“向前挤一挤”。

(2) insert方法导致it迭代器从当前位置到容器末尾所有元素迭代器全部失效
int main() 
{
   vector<int> a{1, 2, 3};
   auto it = a.begin();
   a.insert(a.begin(),0);
   for(; it != a.end(); it++)
       cout<<*it<<endl;
   return 0;
}

输出:

-801756655
402654306
0
1
2
3

同理,插入一个元素之后,在插入位置之后的所有元素都要往后”挪一挪“,因为序列容器存储空间是连续的。那么还用原来老的it迭代器自然而然失效了。

(3) 解决方案

STL标准库中为insert和erase方法提供了返回值,会返回一个插入/删除位置之后的新迭代器,这样就避免了迭代器失效的问题。例程如下:

int main() 
{
   vector<int> a{1, 2, 3,4,5};
   for(vector<int>::iterator it=a.begin();it!=a.end();)
   {
       if(*it%2==1)
       //使用一个it接受返回的新的迭代器,实现自增
           it = a.erase(it);
       else
           it++;
   }
   for (auto elem:a)
       cout<<elem<<endl;
}

输出:

2
4

2. 链表式容器失效原因

对于链表式容器(如 list),删除当前的 iterator,仅仅会使当前的 iterator 失效,这是因为 list 之类的容器,使用了链表来实现,插入、删除一个结点不会对其他结点造成影响。只要在 erase 时,递增当前 iterator 即可,并且 erase 方法可以返回下一个有效的 iterator。
方式一:递增当前it

int main() 
{
   list<int> a{1, 2, 3,4,5};
   for(list<int>::iterator it=a.begin();it!=a.end();)
   {
       if(*it%2==1)
           a.erase(it++);
       else
           it++;
   }
   for (auto elem:a)
       cout<<elem<<endl;
}

此时 a.erase(it++);所经历的操作分三步走,先把 iter 传值到 erase 里面,然后 iter 自增,然后执行 erase,所以 iter 在失效前已经自增了。故可行
方式二:使用erase/insert返回的迭代器

 int main() 
{
   list<int> a{1, 2, 3,4,5};
   for(list<int>::iterator it=a.begin();it!=a.end();)
   {
       if(*it%2==1)
       //使用一个it接受返回的新的迭代器,实现自增
           it = a.erase(it);
       else
           it++;
   }
   for (auto elem:a)
       cout<<elem<<endl;
}

3. 关联式容器失效原因

对于关联容器(如 map, set,multimap,multiset),删除当前的 iterator,仅仅会使当前的 iterator 失效,只要在 erase 时,递增当前 iterator 即可。这是因为 map 之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响,但是原来的it会过期。
P.S.:其中map的erase 和insert会返回一个pair :

  • 第一个元素 (pair.first) 是一个迭代器,指向新插入的元素或尝试插入的元素的位置。
  • 第二个元素 (pair.second) 是一个布尔值,表示插入是否成功。如果插入成功,该值为 true;如果插入失败(因为键值已存在),则该值为 false。
    而我们则要采用erase(iter++)的方式来遍历容器防止非法访问野指针。
for (iter = dataMap.begin(); iter != dataMap.end(); )
{
         int nKey = iter->first;
         string strValue = iter->second;

         if (nKey % 2 == 0)
         {
               map<int, string>::iterator tmpIter = iter;
           iter++;
               dataMap.erase(tmpIter);
               //dataMap.erase(iter++) 这样也行

         }else
     {
          iter++;
         }
}

4. 容器扩容导致的迭代器失效

如果容器扩容,在其他地方重新又开辟了一块内存。原来容器底层的内存上所保存的迭代器全都失效了。
解决方案
在扩容后重新获取一对迭代器就好了,谨慎使用扩容前的迭代器。

参考:

https://www.cnblogs.com/linuxAndMcu/p/14621819.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值