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

本文详细介绍了在C++ STL容器中遍历并删除元素时如何正确更新迭代器的方法,包括list、vector、map和set等容器的正向与反向遍历技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:http://blog.youkuaiyun.com/kesalin/article/details/24265303


众所周知,在使用迭代器遍历 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 之前就获取指向下一个元素的迭代器。如: 

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


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

[cpp]  view plain  copy
  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 用法相同)

[cpp]  view plain  copy
  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往前(反向的)挪一个位置。也就是说,这一步的删除用法应为:

[cpp]  view plain  copy
  1. c.erase((++rit).base());  

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

[cpp]  view plain  copy
  1. c.erase(--(rit.base();  


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

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

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

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

[cpp]  view plain  copy
  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 用法相同):

[cpp]  view plain  copy
  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,删除用法相信大家都明白了,但是,但是,引起迭代器失效的操作还有插入操作呀,相信聪明的你一定能够举一反三正确更新迭代器~~
<think>我们正在解决C++中map的反向迭代器向正向迭代器转换导致的编译错误问题。 根据引用[1][2][3],我们知道反向迭代器是通过`std::reverse_iterator`模板实现的,它包装了一个正向迭代器,但移动方向相反。 在C++标准库中,反向迭代器提供了`base()`成员函数来获取其对应的底层正向迭代器。但是,需要注意的是,这个正向迭代器指向的是反向迭代器当前指向元素的下一个元素(因为反向迭代器的设计是对称的)。 例如,对于一个反向迭代器`rit`,它实际指向的元素是`*rit`,而`rit.base()`返回的正向迭代器指向的是`rit`后面的一个元素(在正向序列中)。 因此,如果我们想要通过反向迭代器获得一个指向相同元素的正向迭代器,我们需要对`base()`返回的迭代器进行适当的调整。 具体到map(或set)这样的关联容器,它们的迭代器是双向迭代器,我们可以进行这样的转换。 问题描述:当我们尝试将map的反向迭代器直接转换为正向迭代器时,可能会遇到编译错误,因为反向迭代器和正向迭代器是不同类型。 解决方案: 1. 使用`base()`成员函数获取对应的正向迭代器。 2. 由于`base()`返回的迭代器指向反向迭代器下一个位置,所以如果我们想要用正向迭代器指向反向迭代器当前指向的元素,我们需要将`base()`返回的迭代器向前移动一个位置(对于反向迭代器指向的最后一个元素,其`base()`返回的是end(),所以不能直接减,需要判断)。 但是,在map中,由于键是唯一的,且迭代器是双向的,我们可以使用递减操作。但是要注意边界情况(如当反向迭代器等于rend()时,其base()是begin(),此时不能递减)。 通常,如果我们有一个反向迭代器`rit`(它不等于`rend()`),那么对应的正向迭代器应该是`--rit.base()`。因为`rit.base()`指向的是`rit`指向元素的下一个元素,所以前移一个就是`rit`指向的元素。 然而,直接写`--rit.base()`可能有问题,因为`base()`返回的是一个右值(临时对象),我们不能直接修改它(递减操作要求左值)。所以我们需要先保存`base()`的结果,然后再递减。 因此,正确的转换方式为: iterator it = --(rit.base()); 或者,我们可以使用一个临时变量: iterator base_it = rit.base(); --base_it; 注意:如果`rit`是`rend()`,那么`rit.base()`就是`begin()`,此时递减会导致未定义行为(因为不能对begin()进行递减)。因此,在操作前确保`rit`不是`rend()`。 另外,在C++中,我们也可以使用标准库中的`std::prev`函数(需要包含<iterator>)来安全地获取前一个迭代器(但同样需要确保不是rend()): iterator it = std::prev(rit.base()); 但是,同样要注意边界。 所以,总结一下,转换步骤: 1. 确保反向迭代器`rit`是有效的,且不等于`rend()`。 2. 使用`rit.base()`获得一个正向迭代器,然后将其递减(使用`--`或`std::prev`)得到指向相同元素的正向迭代器。 示例代码: std::map<int, std::string> mymap; // ... 填充mymap // 获取一个反向迭代器,指向最后一个元素 auto rit = mymap.rbegin(); // 转换为正向迭代器 if (rit != mymap.rend()) { std::map<int, std::string>::iterator it = rit.base(); --it; // 现在it指向rit原来指向的元素 // 或者:auto it = std::prev(rit.base()); // 使用it } 但是,如果我们的反向迭代器是通过循环等方式得到的,我们需要注意当前迭代器的位置。 另外,如果我们只是想要遍历并删除某个元素,我们需要注意迭代器失效的问题。在map中,删除元素只会使指向被删除元素的迭代器失效,其他迭代器仍然有效。因此,我们可以先保存下一个迭代器,然后再删除。 针对用户的问题,编译错误可能是因为直接尝试将反向迭代器赋值给正向迭代器(类型不匹配),或者在使用`base()`之后没有调整位置而导致类型不匹配(因为`base()`返回的是正向迭代器,但位置不对,直接使用可能不符合预期)。 因此,请检查你的代码中是否使用了正确的转换方式。 如果问题仍然存在,请提供具体的错误信息和相关代码片段,以便进一步分析。 下面,我们将生成相关问题。</think>### C++ map 中反向迭代器向正向迭代器转换的编译错误解决方案 在 C++ 中,`std::map` 的反向迭代器(`reverse_iterator`)和正向迭代器(`iterator`)属于不同类型,直接转换会导致编译错误。核心问题在于**反向迭代器底层通过包装正向迭代器实现方向逆转**,两者不存在隐式转换关系[^2][^3]。 #### 错误示例分析 ```cpp std::map<int, std::string> myMap{{1, "A"}, {2, "B"}}; auto rit = myMap.rbegin(); // 反向迭代器 std::map<int, std::string>::iterator it = rit; // 错误!类型不匹配 ``` 编译器会报错:`cannot convert 'reverse_iterator' to 'iterator'`。 --- ### 正确转换方法 #### 方法 1:使用 `base()` 成员函数 反向迭代器提供 `base()` 方法返回底层正向迭代器,但需注意**指针偏移问题**: ```cpp auto rit = myMap.rbegin(); // 指向最后一个元素 {2, "B"} auto base_it = rit.base(); // 返回正向迭代器,指向 rit 的下一个位置(即 end()) // 需要回退一位才能指向原元素 auto it = std::prev(base_it); // 现在指向 {2, "B"} std::cout << it->first; // 输出 2 ``` [^2] 中明确指出:反向迭代器的 `rbegin()` 对应正向迭代器的 `end()`,`rend()` 对应 `begin()`[^2]。 #### 方法 2:直接计算位置 ```cpp auto rit = myMap.rbegin(); auto it = myMap.find(rit->first); // 通过 key 查找 ``` --- ### 关键注意事项 1. **边界安全** 对 `rend()` 调用 `base()` 会返回 `begin()`,此时 `std::prev` 将导致未定义行为: ```cpp auto rit = myMap.rend(); // 指向 begin() 前一位 auto base_it = rit.base(); // 返回 begin() // std::prev(base_it); // 错误!越界 ``` 2. **容器修改** 转换后若删除元素,需先保存下一个迭代器: ```cpp auto next_it = std::next(it); myMap.erase(it); it = next_it; ``` 3. **类型显式声明** 使用 `auto` 易混淆类型,推荐显式声明: ```cpp std::map<int, std::string>::reverse_iterator rit = myMap.rbegin(); std::map<int, std::string>::iterator it = std::prev(rit.base()); ``` --- ### 错误排查表 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | `cannot convert 'reverse_iterator' to 'iterator'` | 隐式类型转换 | 使用 `base()` + 偏移 | | `operator-- not defined` | 对 `begin()` 执行 `--` | 检查是否为 `rend()` | | `dereferencing end iterator` | 解引用无效迭代器 | 验证 `rit != myMap.rend()` | > 关键点:反向迭代器通过包装正向迭代器实现方向逆转,`base()` 返回的迭代器需要偏移修正才能准确定位[^1][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值