向容器中添加元素或删除元素会导致指向容器元素的迭代器、指针或引用失效。
一个失效的迭代器、引用、或指针不再指向任何元素。
向容器中添加元素insert
- vector和string:
1、如果插入元素之后,容器的大小(size())超过了容器的预分配空间(即capacity()属性),容器的存储空间需要重新分配,则指向元素的迭代器、指针和引用都失效;(关于capacity和size的区别在之前的blog有提到)
2、如果插入元素并没有导致存储空间重新分配,则插入元素之前的迭代器、指针和引用依旧有效;当前插入元素以及插入元素位置之后的迭代器、指针和引用都失效。
vector和string是一个顺序容器,在内存中是一块连续的内存空间,当删除或插入一个元素时,内存中的元素会发生移动,以保持空间的连续性;所以在插入删除操作之后,其他元素的地址发生了变化,之前获取的迭代器根据原有的信息就访问不到正确的数据。
还存在capacity < size的情况,这时候需要分配新的空间来保存已有元素和新元素,将已有元素移动到新的空间,然后添加新元素,释放旧的存储空间,理所当然之前的所有迭代器都失效了。
- deque
1、插入到首尾位置之外的任何元素,都会导致迭代器失效;
2、插入到首尾位置时,指向容器元素的迭代器会失效,但是指向元素的引用和指针不会失效。
- list和forward_list
1、无论插入到什么位置,迭代器仍然有效
从容器中删除元素erase
- vector和string
1、指向删除元素之前的迭代器依旧有效;指向删除元素之后的迭代器失效;
- deque
1、在首位置删除元素,迭代器都不会失效;
2、在尾位置删除元素,尾后迭代器失效,其他迭代器不会失效;
3、在除了首尾位置之外删除元素,所有迭代器都会失效。
deque容器迭代器失效的特殊性和deque的结构有关系;
因为:deque是一个队列,队列在首尾位置删除或插入元素的时候,是通过头指针和尾指针不断移动,实现元素的查找并进行相应操作的的。在这期间,元素并没有发生移动,只是头、尾指针不断在移动。所以在头位置删除元素后,并不会改变其他位置的迭代器;在尾位置删除元素后,只会使尾后迭代器失效,因为尾指针向前移动了一个位置,尾后迭代器改变了位置。
而在其他位置进行插入或删除操作时,必然会造成元素的移动;其实,在队列的操作中,并不允许在中间位置进行相关操作的,这不符合队列(FIFO)的特性,所以迭代器都会失效;
- list和forward_list
借用STL迭代器失效
结论:在STL里,我们不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的,删除了之后,该迭代器就失效了,在对其重新赋值之前,不能再访问此迭代器。
避免迭代器失效的方法
1、在循环体内,保证每个循环步都更新迭代器
推荐使用insert和erase来插入和删除元素。因此它们返回指向元素的迭代器,在进行这两个操作时,更新迭代器;
vector<int> v={0,1,2,3,4,5,6,7,8,9};
auto iter=v.begin();
while(iter != v.end())
{
if(*iter % 2 !=0)
{
iter=v.insert(iter,*iter++);//使用insert的返回值更新iter
iter++;
}
else{
iter=v.erase(iter); //使用erase的返回值更新iter
}
}
2、不要保存end()尾后迭代器
vector <int> v={1,2,3,4,5,6,7,8};
auto iter=v.begin();
auto end=v.end();
int i=0;
while(iter != end)
{
v.push_back(i++);
}
上面的例子就是一个错误的典型,在循环体外end保存了容器的尾后迭代器,但在循环体内部插入了元素,尾后迭代器失效了,end也就不再指向容器中的任何元素了。
因此,在使用end()尾后迭代器的时候,必须在循环体内部,反复调用end(),不能在循环体外面保存尾后迭代器。
本文参考了:
http://www.jb51.net/article/100683.htm
http://blog.csdn.net/lujiandong1/article/details/49872763
http://www.cnblogs.com/qiaoconglovelife/p/5370396.html