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
今日推荐
周排行