C++ 中STL迭代器失效问题的剖析



STL中容器分为顺序容器和关联容器,顺序容器有:vector、deque、list;关联容器有:set、map、multiset、multimap。

一、vector、deque、list容器

在vector、deque、list 中遍历删除某些元素时可以使用下面方式

(1)正确用法1

通过erase方法的返回值来获取下一个元素的位置。

JAVA;">

std::vector

 

    Vec;

std::vector

  

    ::iterator itVec; for(itVec = Vec.begin(); itVec != Vec.end(); ) { if (WillDeleteCondition(*itVec)) itVec = Vec.erase(itVec); else itVeC++; }

   

JAVA;">

std::list

 

    List;

std::list

  

    ::iterator itList; for(itList = List.begin(); itList != List.end(); ) { if (WillDeleteCondition(*itList))

    itList = List.erase(itList); else itList++; }

   

(2)对于list也可以使用下列方法

在调用erase方法之前先使用"++"来获取下一个元素的位置

JAVA;">

std::list

 

    List;

std::list

  

    ::iterator itList; for(itList = List.begin(); itList != List.end(); ) { if (WillDeleteCondition(*itList))

     List.erase(itList++); else itList++; }

   

(3)错误用法

在调用erase方法之后使用"++"来获取下一个元素的位置,由于在调用erase方法之后,该元素的位置已经被删除,如果再根据这个旧的位置来获取下一个位置,则会出现异常。

JAVA;">

std::list

 

    List;

std::list

  

    ::iterator itList; for(itList = List.begin(); itList != List.end();

    itList++) { if (WillDeleteCondition(*itList))

    List.erase(itList); }

   

二、set、map容器

在set、map中遍历删除某些元素时可以使用下面方式

(1)正确写法A

JAVA;">

std::map

 

    Map

std::map

  

    ::iterator itMap for(itMap = Map.begin(); itMap != Map.end(); ) { if (WillDeleteCondition(itMap->second))

    Map.erase(itMap++); else itMap++; }

   

三、剖析原因

C++标准中,顺序容器的erase函数会返回iterator,但关联容器的erase函数不返回iterator。

(1)对于顺序容器 vector、deque,删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector、deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。erase方法可以返回下一个有效的iterator。【即正确用法1】

(2)对于关联容器map、set、multimap、multiset,删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。【即正确用法A】

(3)对于顺序容器list,erase方法可以返回下一个有效的iterator【即正确用法1】。由于list是一个链表,删除当前的iterator,仅仅会使当前的iterator失效,所以也可以在erase时,递增当前的iterator【即正确用法A】。

(4)erase函数返回被删除元素的下一个元素的迭代器。在STL中,不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的。

四、迭代器失效的情况

(1)vector

内部数据结构:数组

随机访问每个元素,所需要的时间为常量。

在末尾add/delete元素所需时间与元素数目无关,在开头或中间add/delete元素所需时间随元素数目呈线性变化。

可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。

add元素:所有元素的迭代器失效(内存重新分配时)(当把超过capacity()-size()个元素插入vector中时,内存会重新分配)

当前元素以后的任何元素的迭代器失效(内存没有重新分配时)

delete元素:被删除元素以后的任何元素的迭代器都将失效

(2)deque

内部数据结构:数组

随机访问每个元素,所需要的时间为常量

在开头或末尾add/delete元素所需时间与元素数目无关,在中间add/delete元素所需时间随元素数目呈线性变化。

可动态增加或减少元素,内存管理自动完成,不提供用于内存管理的成员函数。

add元素:迭代器失效

delete元素:迭代器失效(删除中间元素)

指向该元素的迭代器失效(删除头/尾元素)

(3)list

内部数据结构:双向环状链表

不能随机访问一个元素,可双向遍历。

在开头/中间/末尾add/delete元素所需时间都为常量

可动态增加或减少元素,内存管理自动完成。

add元素:不会使迭代器失效

delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效

(4)set

内部数据结构:红黑树

键和值相等,键唯一,元素默认按升序排列。

add元素:不会使迭代器失效

delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效

(5)map

内部数据结构:红黑树

键唯一,元素默认按键的升序排列。

add元素:不会使迭代器失效

delete元素:指向当前被删除元素的迭代器失效,其它迭代器都不会失效

五、实例

JAVA;">

// iteratorInvalidateVector.cpp

#include

 

  

#include

  

     typedef std::vector

   

      Vector; typedef std::vector

    

      ::iterator VectorIt; void printLog(Vector vectOne) { VectorIt it; for (it = vectOne.begin(); it != vectOne.end(); it++) { std::cout<< *it<<" "; } std::cout<

      JAVA;">//iteratorInvalidateList.cpp

#include

       

           

#include

        

           typedef std::list

         

            List; typedef std::list

          

            ::iterator ListIt; void printLog(List listOne) { ListIt it; std::cout<

            JAVA;">// iteratorInvalidateMap.cpp

#include

             

              

#include

                #include

               

                  typedef std::map

                

                   Map; typedef std::map

                 

                   ::iterator MapIt; void printLog(Map m) { MapIt it; for (it = m.begin(); it != m.end(); it++) { std::cout<< it->second <<" "; } std::cout<

                  

                    second % 2)) { m.erase(it++); } else { it++; } } } void deleteValueOne(Map &m, int n) { MapIt it; for (it = m.begin(); it != m.end(); it++) { if (0 == (it->second % 2)) m.erase(it); } } int main() { Map m; int i = 0; for(i = 0; i < 21; i++) { m[i] = i; } m[3] = 4; printLog(m); deleteValue(m, 5); printLog(m); return 0; }

                   

             注意:在iteratorInvalidateMap.cpp 中如果使用函数deleteValueOne(),并不会造成crash,但没有得到正确的结果,得到的结果如下

             0 1 2 4 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

             1 4 5 7 9 11 13 15 17 19

             因为(it指向2时)在执行m.erase(it)之后,it自动绑定到下一个元素(第一个4),此时又执行for循环中的it++(it指向第二个4),就跳过了map中的第一个4。

            

      

猜你喜欢

转载自right-left.iteye.com/blog/2406715