在使用vector的时候我们可以会遇到需要在已有对象的某个数据之前插入一个数据再对被插入的数据进行修改操作,情况如下:
这段代码在编译器的过程中并不会出现问题,可是一旦运行程序就会出现程序崩溃,这里就是迭代器失效的第一种情况:当我们在找到找到某个位置的时候,实际上找到的是这个数据所在的地址,我们insert操作本质上是把所有的数据往后移动了一个位置,把这个位置空出来,再把我们想要插入的数据插入到这个位置上,当insert函数结束的时候,it这个迭代器指向的还是之前的那个位置,但是这个位置所代表的数据已经改变了,所以vs的编译器进行了强制的检查,发现这个迭代器指向的数据已经发生了改变,让这个迭代器失效了,所以程序发生了崩溃。
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.reserve(6);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
v.insert(it, 5);
}
*it *= 10;
for (auto x : v)
{
cout << x << " ";
}
return 0;
}
第二种迭代器失效的情况和第一种很类似,但是发生在容器需要进行扩容的时候,假如这里我们自己实现了一个insert函数,这个insert函数的功能和库里的函数功能一致,也是在指定位置之前加上指定的数据,如果我们没有对指定位置的这个迭代器进行处理,如果正好碰到对象需要扩容的时候,程序也会出现错误。
因为当对象的容量不够的时候,我们要在堆的另外一块空间上申请一块更大的空间,把原本对象里的数据拷贝到新的空间里,再回收原来的空间。这个时候问题就出现了,it迭代器指向的还是旧空间的位置,而我们的对象已经移动到了一个新的空间,这个时候插入数据就出现问题了,这是第一个问题,这个问题可以在insert函数里用it迭代器于对象begin迭代器的相对距离来解决。
解决了上面的问题以后,main函数里的it迭代器还是指向的是旧空间的位置,但是这个位置的空间已经被释放掉了,所以这个迭代器也就变成了类似于野指针的存在。
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
v.insert(it, 5);
}
*it *= 10;
for (auto x : v)
{
cout << x << " ";
}
return 0;
}
第三种迭代器失效的情况是发生在删除数据的时候,如果在使用迭代器的方式删除某个位置的数据以后,想要更改进入这个位置的数据时,也会出现程序的崩溃。
这个崩溃的原因和第一个问题出现的原因是类似的,也是因为迭代器指向的位置的意义发生的改变,我们删除这个数据的本质是把这个数据之后的所有数据向前移动一位,这样要删除的数据就会被覆盖,实现被删除的效果,也是因为这个原因,虽然it原本指向的那个位置的数据已经被删除了,但是it这个迭代器还是指向这个位置,并没有消失,但是这个位置的意义已经发生改变了,vs环境下的代码进行了强制检查,所以程序出现了崩溃。同时不仅是这个it迭代器失效了,如果还存在指向被删除位置之后的迭代器,那么这些迭代器也都一并失效了。
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.reserve(6);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
v.erase(it);
}
*it *= 10;
for (auto x : v)
{
cout << x << " ";
}
return 0;
}
所以如果在使用迭代器方式对容器进行插入或者删除以后,就最好不要再操作这个迭代器了,因为这个迭代器在大多数情况下都会失效,如果还想要使用这个迭代进行操作的话,可以对这个迭代器进行一次赋值,如下:
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
it = v.insert(it, 5);
}
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
it = v.erase(it);
}
要注意的是insert的返回值是指向被插入的那个位置,也就是指向新插入的数据;erase的返回值则是被数据删除之前,被删除的位置的下一个数据,也就是重新进入被删除的位置的数据。