在遍历中使用 iterator/reverse_iterator 进行 Erase 的用法

本文详细介绍在遍历STL容器(如list、vector、map、set)时,如何使用正向与反向迭代器安全地删除元素。针对不同容器的特点,提供了具体的代码示例,帮助读者理解迭代器失效的问题及解决方法。

转自  http://blog.youkuaiyun.com/kesalin/article/details/24265303


在遍历中使用 iterator/reverse_iterator 进行 Erase 的用法
本文遵循“ 署名-非商业用途-保持一致”创作公用协议

众所周知,在使用迭代器遍历 STL 容器时,需要特别留意是否在循环中修改了迭代器而导致迭代器失效的情形。下面我来总结一下在对各种容器进行正向和反向遍历过程中删除元素时,正确更新迭代器的用法。本文源码:https://code.youkuaiyun.com/snippets/173595

首先,要明白使用正向迭代器(iterator)进行反向遍历是错误的用法,要不干嘛要有反向迭代器呢(reverse_iterator)。其次,根据容器的特性,遍历删除操作的用法可以分为两组,第一组是 list 和 vector,第二组是 map 和 set。


接下来,看看具体怎么个用法。

第一种情形:正向遍历删除元素

对 list 和 vector 来说,它们的 erase 函数会返回下一个迭代器,因此在遍历时,只需要 it = c.erase(it); 即可。

对 map 和 set 来说,它们的 erase 函数返回的 void,而在进行 erase 之后,当前迭代器会失效,无法再用于获取下一个迭代器。因此需要 erase 之前就获取指向下一个元素的迭代器。如: 

  1. tmpIt = it;  
  2. ++it;  
  3. c.erase(tmpIt);  
利用后缀++操作符的特性(先创建副本,然后再递增迭代器,然后返回副本)上面的三行代码可以简化为一行:
  1. c.erase(it++);  


list 正向遍历删除元素示例(vector 用法相同):

  1. list<int>::iterator it;  
  2. for (it = l.begin(); it != l.end();)  
  3. {  
  4.     if (0 == (*it) % 2) {  
  5.         it = l.erase(it);  
  6.     }  
  7.     else {  
  8.         ++it;  
  9.     }  
  10. }  

map 正向遍历删除元素示例(set 用法相同)

  1. map<intint>::iterator mit;  
  2. for (mit = m.begin(); mit != m.end();)  
  3. {  
  4.     if (0 == mit->first % 2) {  
  5.         m.erase(mit++);  
  6.     }  
  7.     else {  
  8.         ++mit;  
  9.     }  
  10. }  

第二种情形,反向遍历删除元素

关于正向/反向迭代器的关系,请参考《Effective STL》,在这里我只说明一点,两者相差一个元素,从一个反向迭代器获得对应的正向迭代器需要使用 base() 方法。如下图所示:ri 是指向元素3的反向迭代器,而 i 是 ri.base() 所得到的正想迭代器。


由于所有的 erase 函数都只接受正向迭代器 iterator,所以在进行反向遍历删除元素时,首先需要将 reverse_iterator 转换为 iterator,然后再考虑更新迭代器的问题。

先来分析如何将 reverse_iterator 转换为 iterator。如上图所示,我们想要删除元素3,而 ri.base() 所得到的正向迭代器 i 指向的其实 4 了,因而为了正确地删除元素 3,需要将ri往前(反向的)挪一个位置。也就是说,这一步的删除用法应为:

  1. c.erase((++rit).base());  

或:(想想为什么?,但这个用法不具备可移植性,因为有些 STL 实现不允许修改函数返回的指针)

  1. c.erase(--(rit.base();  


然后,我们来分析迭代器更新的问题。
对 list/vector 来说,由于的 erase 能够返回一个有效的正向迭代器,因而只需要将返回的正向迭代器转换为反向迭代器即可。

对 map/set 来说,因为在进行删除操作 l.erase((++rit).base()) 时,迭代器已经更新过了,真是一举两得啊。从这里也可以看出,使用这种先递增后 base() 的转换删除法,代码更清晰。

至此,理论分析完毕,下面我们来看具体的实例。

list 反向遍历删除元素示例(vector 用法相同):

  1. // erase with reverse_iterator  
  2. list<int>::reverse_iterator rit;  
  3. for (rit = l.rbegin(); rit != l.rend();)  
  4. {  
  5.     if (0 == (*rit) % 2) {  
  6.         rit = list<int>::reverse_iterator(l.erase((++rit).base()));  
  7.     }  
  8.     else {  
  9.         ++rit;  
  10.     }  
  11. }  

map 反向遍历删除元素示例(set 用法相同):

  1. // erase with reverse_iterator  
  2. map<intint>::reverse_iterator rit;  
  3. for (rit = m.rbegin(); rit != m.rend();)  
  4. {  
  5.     if (0 == rit->first % 2) {  
  6.         m.erase((++rit).base());  
  7.     }  
  8.     else {  
  9.         ++rit;  
  10.     }  
  11. }  

OK,删除用法相信大家都明白了,但是,但是,引起迭代器失效的操作还有插入操作呀,相信聪明的你一定能够举一反三正确更新迭代器~~


在C++中,使用 `vector` 的迭代器(`iterator`)可以方便地遍历容器中的元素。以下是一些常见的遍历方法及其示例代码。 ### 使用 `begin()` 和 `end()` 进行正向遍历 这是最常见的一种方式,通过 `begin()` 获取指向容器首元素的迭代器,通过 `end()` 获取指向容器尾后位置的迭代器。 ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; } ``` ### 使用显式类型声明的迭代器 在某些情况下,可能需要显式声明迭代器的类型,这在模板编程或需要更明确类型控制的场景中较为常见。 ```cpp #include <iostream> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4, 5}; for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; } ``` ### 使用 `rbegin()` 和 `rend()` 进行反向遍历 如果需要从容器末尾向首部遍历,可以使用反向迭代器 `reverse_iterator`,通过 `rbegin()` 和 `rend()` 获取反向遍历的起始和结束位置。 ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) { std::cout << *rit << " "; } std::cout << std::endl; return 0; } ``` ### 使用 `const_iterator` 遍历(适用于只读访问) 当不需要修改容器中的元素时,可以使用 `const_iterator` 来确保不会意外修改元素。 ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (std::vector<int>::const_iterator cit = vec.cbegin(); cit != vec.cend(); ++cit) { std::cout << *cit << " "; } std::cout << std::endl; return 0; } ``` ### 在遍历过程中删除元素 如果在遍历过程中需要删除某些元素,应特别注意避免迭代器失效。以下是一个安全删除元素的示例: ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto it = vec.begin(); it != vec.end();) { if (*it == 3) { it = vec.erase(it); // 删除元素,并更新迭代器 } else { ++it; } } for (const auto& val : vec) { std::cout << val << " "; } std::cout << std::endl; return 0; } ``` 上述代码中,使用 `erase()` 删除元素时,返回值是下一个有效迭代器的位置,从而避免了迭代器失效的问题[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值