迭代器失效问题
文章目录
迭代器失效无非两个语义 两个语义参考:
1.原来迭代器指向一个有效元素,在进行某个操作之后,迭代器指向了一块无效内存 参考
1)有可能是v.end()在pop_back()一个元素之后,v.end()指向了一块无效内存
2)也有可能是容器扩容,导致原容器的所有迭代器指向一块已经被释放的(无效的)内存
2.在某位置进行插入或删除操作之后,当前位置以及当前位置后面的所有迭代器失效,因为当前迭代器指向的已经不是原来的元素,这种情况也会导致迭代器失效。
但是是否会报错,不同的编译器有不同的实现。
这种情况下,迭代器并不是指向了一块无效内存,实际上还是指向了一块有效内存,但是迭代器指向的元素并不是它原来所指向的元素。
比如,一个拥有10个元素的vector,元素为从0到10。设置迭代器为v.begin(),此时v.begin()指向元素0。
在删除第一个元素之后,后面的所有元素都向前移一位,这时迭代器是指向0的下一个元素1的,这是一块有效的内存。
但是vs2017编译器判断迭代器是否失效的标准不是当前迭代器是否指向了一块有效内存(当然如果是无效内存那么迭代器一定是失效的),而是看当前迭代器的指向是否发生了改变。
其具体实现是维护了一个迭代器链表,如果当前迭代器的指向不是它原来的指向,就把当前迭代器的内部指针置为空指针,这样就会引发异常。参考
而在Linux下的GCC编译器遇到这种情况则不会认为是迭代器失效的情况,只要迭代器指向的一块有效内存就没问题。当然并不是说这就不能算是迭代器失效的一种情况,这种情况往往是在项目的具体实现中有可能会引发未知的且很难排查的逻辑错误。
所以不能因为GCC编译器不认为这是错误就这样写,还是要避免这种让迭代器语义失效的写法。参考
下面分别介绍不同类型容器的迭代器失效情况
一、序列式容器
iter = v.erase(iter);
1.vector
可能会导致vector迭代器失效的操作有erase和insert
1)erase(第二种语义)
错误写法
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
v.reserve(20);
cout << "erase之前:";
for (auto i : v) {
cout << i << " ";
}
cout << endl;
for (auto iter = v.begin(); iter != v.end();) {
if (*iter == 3) {
//如果不用iter接住erase的返回值,则当前iter失效
//则下次再进入for循环时执行iter++操作时程序崩溃(因为迭代器已失效,不能执行++操作)
v.erase(iter);
}
else {
iter++;
}
}
cout << "erase之后:";
for (auto i : v) {
cout << i << " ";
}
cout << endl;
return 0;
}
正确写法
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
v.reserve(20);
cout << "erase之前:";
for (auto i : v) {
cout << i << " ";
}
cout << endl;
for (auto iter = v.begin(); iter != v.end();) {
if (*iter == 3) {
//iter接住erase返回的下一个重新分配的有效迭代器即可
iter = v.erase(iter);
}
else {
iter++;
}
}
cout << "erase之后:";
for (auto i : v) {
cout << i << " ";
}
cout << endl;
return 0;
}
为什么序列式容器不能像关联式容器一样使用v.erase(iter++)
解决迭代器失效问题呢?
因为erase当前位置之后,当前位置包括当前位置之后的所有迭代器全部失效
所以iter即使++了,也是++到了一个失效的迭代器,所以不能使用这种方式防止迭代器失效
(而关联式容器能使用这种方式的原因是因为erase当前元素只会让当前迭代器失效,不会影响到其他迭代器)
2)insert
vector的insert操作不一定会导致迭代器失效
具体要看迭代器的位置,插入的位置以及是否会导致扩容
I、情况一(第一种语义)
insert操作在vector发生扩容之后,原vector所有迭代器失效(无论位置)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(3);
v.reserve(4);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
auto iter = v.begin();
cout << *iter << endl;
//这个插入操作没有引起vector扩容,所以迭代器未失效
v.push_back(0);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
cout << *iter << endl;
//再push_back就会发生扩容,所有原vector的迭代器都会失效
v.push_back(0);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl;
//迭代器失效
//cout << *iter << endl;
return 0;
}
解决办法:重新获取迭代器
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(3);
v.reserve(4);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
auto iter = v.begin();
cout << *iter << endl;
//这个插入操作没有引起vector扩容,所以迭代器未失效
v.push_back(0);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
cout << *iter << endl;
//再push_back就会发生扩容,所有原vector的迭代器都会失效
v.push_back(0);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl;
//重新获取迭代器
iter = v.begin();
cout << *iter << endl;
return 0;
}
II、情况二(第二种语义)
在insert位置之前的迭代器都有效,而在insert位置(包括insert位置)之后的所有迭代器失效
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//reserve扩大容量,保证不发生扩容
v.reserve(20);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
auto iter = v.begin() + 4;
cout << "v.begin():" << *(v.begin()) << endl;
cout << "v.begin() + 4:" << *iter << endl << endl;
//插入操作
v.insert(v.begin() + 4, 15);
cout << "v.data():" << v.data() << endl;
cout << "v.size():" << v.size() << endl;
cout << "v.capacity():" << v.capacity() << endl << endl;
//插入位置之前的迭代器没有失效
cout << "v.begin():" << *(v.begin()) << endl;
//插入位置之后(包括插入位置)的迭代器都失效
//cout << "v.begin() + 4:" << *iter << endl;
return 0;
}
解决方法:同上
2.deque
情况类比vector,不过,deque不会扩容
1)insert
增加任何元素都将使deque的迭代器失效。
2)erase
在deque的中间删除元素将使迭代器失效。
在deque的头或尾删除元素时,只有指向该元素的迭代器失效。
二、树结构容器
map.erase(iter++);
iter = map.erase(iter);
对于树结构容器来说,insert与erase操作只会导致当前位置迭代器失效,不会影响到其他位置的迭代器(因为其底层是红黑树,空间并不是连续的)
1.map
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<int, int> map;
map.insert({ 2, 12 });
map.insert({ 1, 11 });
map.insert({ 4, 14 });
map.insert({ 3, 13 });
auto iter = map.begin();
cout << "erase前:" << endl;
for (; iter != map.end(); iter++) {
cout << iter->first << " " << iter->second << endl;
}
cout << endl;
for (iter = map.begin(); iter != map.end(); ) {
if (iter->first == 2) {
//错误写法:map.erase(iter);
//即删除当前位置元素之后,iter没有移动到下一个位置的迭代器,而此时当前迭代器已经失效
//下次再进入for循环,执行iter++操作时就会导致程序崩溃,因为已经失效的迭代器不能执行++操作
//正确写法1:
map.erase(iter++);
//正确写法2:
//原理跟正确写法1一样,只不过这种是把后++操作展开写了
//总之要让map进行erase操作之前,iter应该++到下一个位置,并且还得保证map是根据当前位置进行的erase操作
//auto tempIter = iter;
//iter++;
//map.erase(tempIter);
//正确写法3:
//原理同序列式容器
//iter = map.erase(iter);
}
else {
iter++;
}
}
cout << "erase后:" << endl;
for (iter = map.begin(); iter != map.end(); iter++) {
cout << iter->first << " " << iter->second << endl;
}
cout << endl;
return 0;
}
2.set
同上
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> set;
set.insert(2);
set.insert(1);
set.insert(4);
set.insert(3);
auto iter = set.begin();
cout << "erase前:" << endl;
for (; iter != set.end(); iter++) {
cout << *iter << " " << endl;
}
cout << endl;
for (iter = set.begin(); iter != set.end(); ) {
if (*iter == 2) {
//错误写法:set.erase(iter);
//即删除当前位置元素之后,iter没有移动到下一个位置的迭代器,而此时当前迭代器已经失效
//下次再进入for循环,执行iter++操作时就会导致程序崩溃,因为已经失效的迭代器不能执行++操作
//正确写法1:
//set.erase(iter++);
//正确写法2:
//原理跟正确写法1一样,只不过这种是把后++操作展开写了
//总之要让set进行erase操作之前,iter应该++到下一个位置,并且还得保证set是根据当前位置进行的erase操作
//auto tempIter = iter;
//iter++;
//set.erase(tempIter);
//正确写法3:
//原理同序列式容器
//iter = set.erase(iter);
}
else {
iter++;
}
}
cout << "erase后:" << endl;
for (iter = set.begin(); iter != set.end(); iter++) {
cout << *iter << " " << endl;
}
cout << endl;
return 0;
}
三、链表式容器
链表式容器同树结构容器一样,两种方式都可以解决迭代器失效问题
1.list
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> list;
list.push_back(1);
list.push_back(2);
list.push_back(3);
list.push_back(4);
auto iter = list.begin();
cout << "erase前:";
for (; iter != list.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
for (iter = list.begin(); iter != list.end(); ) {
if (*iter == 2) {
//错误写法:
//list.erase(iter);
//正确写法1:
//list.erase(iter++);
//正确写法2:
iter = list.erase(iter);
}
else {
iter++;
}
}
cout << "erase后:";
for (iter = list.begin(); iter != list.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
return 0;
}
2.forward_list
#include <iostream>
#include <forward_list>
using namespace std;
int main()
{
forward_list<int> forward_list;
forward_list.push_front(1);
forward_list.push_front(2);
forward_list.push_front(3);
forward_list.push_front(4);
auto iter = forward_list.begin();
cout << "erase前:";
for (; iter != forward_list.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
for (iter = forward_list.begin(); iter != forward_list.end(); ) {
if (*iter == 2) {
//错误写法:
//forward_list.erase_after(iter);
//正确写法1:
//forward_list.erase_after(iter++);
//正确写法2:
iter = forward_list.erase_after(iter);
}
else {
iter++;
}
}
cout << "erase后:";
for (iter = forward_list.begin(); iter != forward_list.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
return 0;
}