1.1 关于迭代器
对于各种数据结构,由于它们的底层实现不同,它们的遍历也不同。例如数组就可以用指针遍历,但链表却不行。而迭代器就像把容器的遍历操作进行了封装,这样在遍历时,就可以针对迭代器进行编程而不用关心容器本身。但是迭代器也可能会失效!
2.1 vector迭代器失效的几种情况
2.1.1 会引起空间本身改变的操作,都可能造成迭代器失效
用resize、reserve、insert、assign、 push_back举例:
#include <iostream>
#include <vector>
int main()
{
vector<int> v = {0, 1, 2, 3, 4};
vector<int>:: iterator it = v.begin();
//reserve的作用就是改变空间的大小,可能会引起底层空间的改变
reserve(10);
while(it != v.end())
{
cout<<*it<<" ";
++it;
}
cout<<endl;
return 0;
}
运行结果如下(VS2022):
这里显示是触发了一个assert,错误信息是vector迭代器不兼容,也就意味着当前迭代器失效。
调试一下看看具体原因:
直到第4行代码,it与v.begin()还指向同一块空间
执行完第4行,我们可以看到it与v.begin()已经不一样了,这说明it指向的空间已经销毁,v本身开辟了新的空间,这样继续执行代码,解引用it相对于非法访问。
可是VS这里是出发了一个assert,这是由于VS的编译器检查比较严格,迭代器一旦失效会在非法访问前报错。
resize、insert、assign、 push_back操作引起迭代器失效的原因与reserve相同
可以用下面的代码验证:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {0, 1, 2, 3, 4};
vector<int>:: iterator it = v.begin();
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
v.reserve(10);
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(20, 6);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(10);
// 给vector重新赋值,可能会引起底层容量改变
//v.assign(100, 8);
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
2.1.2 指定位置元素的删除操作 —— erase
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int arr[] = { 0, 1, 2, 3 };
vector<int> v(arr, arr + sizeof(arr) / sizeof(int));
// 使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 1);
// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此处会导致非法访问
return 0;
}
运行结果:
这里结果也是一个assert:无法取消引用无效的向量迭代器;
erase并没有改变原来的空间,而是将pos后的元素向pos位置移动,这里报错主要是因为如果删除的是最后一个元素,end()就会前移,pos会指向end(),这个时候如果对pos解引用就会非法访问。VS检查严格,认为删除了元素,迭代器就失效.
如何解决这个问题?
查看一下erase的定义
我们发现erase会返回被删除元素的下一个元素的位置,因此
pos = v.erase(pos);