智慧 + 毅力 = 无所不能

正确性、健壮性、可靠性、效率、易用性、可读性、可复用性、兼容性、可移植性...

导航

如何在遍历中使用 iterator/reverse_iterator 删除元素

Posted on 2014-05-05 09:18  Bill Yuan  阅读(6553)  评论(0编辑  收藏  举报

转自:http://www.cppblog.com/kesalin/archive/2014/04/22/cpp_stl.html

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

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

 

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

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

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

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

tmpIt = it; ++it; c.erase(tmpIt);

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

 

c.erase(it++);

 

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

 

// erase with iterator
list<int>::iterator it;
for (it = l.begin(); it != l.end();)
{
     if (0 == (*it) % 2) {
         it = l.erase(it);
     }
     else {
        ++it;
    }
}

 

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

// erase with iterator
map<int, int>::iterator mit;
for (mit = m.begin(); mit != m.end();)
{
   if (0 == mit->first % 2) {
        m.erase(mit++);
   }
   else {
        ++mit;
    }
}

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

 

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

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

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

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

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

 

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

 

然后,我们来分析迭代器更新的问题。

对 list/vector 来说,由于的 erase 能够返回一个有效的正向迭代器,因而只需要将返回的正向迭代器转换为反向迭代器即可。

对 map/set 来说,因为在进行删除操作 l.erase((++rit).base()) 时,迭代器已经更新过了,真是一举两得啊。

从这里也可以看出,使用这种先递增后 base() 的转换删除法,代码更清晰。 至此,理论分析完毕,下面我们来看具体的实例。 list 反向遍历删除元素示例(vector 用法相同):

 

// erase with reverse_iterator
list<int>::reverse_iterator rit;
for (rit = l.rbegin(); rit != l.rend();)
{
    if (0 == (*rit) % 2) {
        rit = list<int>::reverse_iterator(l.erase((++rit).base()));
        //rit = list<int>::reverse_iterator(l.erase(--(rit.base()));
    }
    else {
        ++rit;
    }
}

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

 

// erase with reverse_iterator
map<int, int>::reverse_iterator rit;
for (rit = m.rbegin(); rit != m.rend();)
{
    if (0 == rit->first % 2) {
        m.erase((++rit).base());
    }
    else {
        ++rit;
    }
}

 

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