前言
关于面试时有被问到过这类问题,当时由于只一知半解,回答的不是特别好,所以今天自己特意来实验一下。希望能帮助大家有同样疑惑的人解答疑惑!
目录
关于迭代器失效的几种情况
迭代器(iterator)类似于指针的效果,比如有自加(it++)、解引用(*it)的效果。对于vector容器,它的底层就是一段连续的空间,所以对于以上两种效果很容易满足,即在vector内部就是一个普通的指针。而对于其他的容器,比如链表(list),它的底层不是一段连续的空间,所以对于自加(it++)这种效果是不能用一个普通指针来进行实现的,所以它把iterator封装成了一个类,在类中对自加(it++)这种操作符进行运算符重载operator ++(),让大家进行迭代器操作时,看似和指针一样的效果。这是关于迭代器的两种不同的实现形式,当然内部还有很多细节就不一一介绍了,大家想了解更多,可以查阅侯捷老师的《STL源码剖析》这本书。
1、序列式容器迭代器失效【vector】
失效情况总结:
- 当使用erase方法后:指向删除节点后的迭代器全部失效
- 当使用push_back方法后:end操作返回的迭代器肯定失效,关于end之前的迭代器则可能失效,也可能不会失效,若push_back操作后,capacity没有变化,则不会失效,如果有变化则会失效。
使用erase
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> q{1,2,3,4,5,6};
//在这里想把大于2的元素都删除
for(auto it=q.begin();it!=q.end();it++){
if(*it>2)
q.erase(it);//这里就会发生迭代器失效
}
//打印结果
for(auto it=q.begin();it!=q.end();it++){
cout<<*it<<" ";
}
cout<<endl;
return 0;
}
解释:对于erase操作后,会返回下一个元素的迭代器,也就是后面的元素会向前移动一个,然后循环体外又进行一次自加,相当于到了下下个元素的位置,因此发生了这种现象。
解决方法如下:
//在这里想把大于2的元素都删除
for(auto it=q.begin();it!=q.end();)
{
if(*it>2)
{
it=q.erase(it);//这里会返回指向下一个元素的迭代器,因此不需要再自加了
}
else
{
it++;
}
}
使用push_back
(1)不会使前面的迭代器失效
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> q;
q.push_back(1);
q.push_back(2);
q.push_back(3);
cout<<"原容器容量:"<<q.capacity()<<endl;
auto it_start=q.begin(),it_end=q.end();
q.push_back(4);//利用push_back添加一个元素
cout<<"push_back后容器容量:"<<q.capacity()<<endl;
cout<<"利用原先迭代器进行遍历容器结果:";
while(it_start!=it_end){
cout<<*it_start<<" ";
it_start++;
}
cout<<endl;
cout<<"利用更新后迭代器进行遍历容器结果:";
for(auto it=q.begin();it!=q.end();it++){
cout<<*it<<" ";
}
cout<<endl;
return 0;
}
解释:我们可以发现,当容器的容量capacity前后没有改变时,只会使end操作符返回的迭代器失效,而之前的迭代器并没有失效。这是因为capacity没有扩充,那么就是在原先分配的内存上继续进行添加元素,所以前面的迭代器不会失效。
(2)会使前面的迭代器也失效
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> q;
q.push_back(1);
q.push_back(2);
q.push_back(3);
q.push_back(4);
cout<<"原容器容量:"<<q.capacity()<<endl;
auto it_start=q.begin(),it_end=q.end();
q.push_back(5);//利用push_back添加一个元素
cout<<"push_back后容器容量:"<<q.capacity()<<endl;
cout<<"利用原先迭代器进行遍历容器结果:";
while(it_start!=it_end){
cout<<*it_start<<" ";
it_start++;
}
cout<<endl;
cout<<"利用更新后迭代器进行遍历容器结果:";
for(auto it=q.begin();it!=q.end();it++){
cout<<*it<<" ";
}
cout<<endl;
return 0;
}
解释:在这里,我们会发现前后两者的容量capacity不一样,利用原先迭代器进行遍历的结果,也和最后用更新后的迭代器进行遍历结果的前4个元素值不一样。这就是所有的迭代器都失效了,原因是因为vector当当前容量不足时,会去另外一段空闲的内存开辟一个是原先容量两倍大的内存,并把原先地方的值依次复制到新的内存中,原先内存即是空闲内存,可被任何程序使用,即是不确定的值,因此利用原先的迭代器进行遍历,最后结果也是不确定的。
2、关联式容器迭代器失效【map】
对于map,当进行erase操作后,只会使当前迭代器失效,不会造成其他迭代器失效,这是因为map底层实现是由红黑树实现的,所以当删除一个元素时,会进行二叉树的调整,但每个节点在内存中的地址是没有改变的,改变的只是他们之间的指针指向。