对于迭代器,其实本质上就是一个指针,那么当这个指针指向的位置已经不是我们想要的位置时,我们认为这个迭代器失效了。
关于迭代器失效,主要有两种场景:
底层空间发生改变即当我们获取了迭代器之后,又对底层的空间进行了操作使其发生了改变,如resize、reserve、insert、push_back等。这样一旦发生了扩容,导致原空间被释放,那么这个迭代器显然失效了。
#include
using namespace std;
#include
int main()
{
vector v{1,2,3,4,5,6};
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
v.insert(v.begin(), 0);
v.push_back();
//此时进行上面这些操作之后,如果使用迭代器打印,便可能发生报错
while(it != v.end())
{
cout<< *it << " " ;
++it;
}
cout<
对于删除元素的操作,并不会发生底层空间的改变,看似迭代器还指向着原来的位置,没有发生什么问题。但迭代器是和空间绑定的吗?并不是的,迭代器绑定的对象应该是元素!那么通过迭代器删除元素之后,迭代器本来指向的元素已经消失,此时指向的只是因为补齐而来的元素。那么,就出现了两个问题:(1)删除操作完成后,迭代器若++,便跳过了补齐来的元素。(2)而如果删除的是最后一个元素,补齐来的元素是end,而end没有数据,解引用时便会报错。而且如果编译器检查不仔细的话,对失效的迭代器++,迭代器便错过了end,就有可能形成死循环的问题。
#include
using namespace std;
#include
int main()
{
int a[] = { 1, 2, 3, 4 };
vector v(a, a + sizeof(a) / sizeof(int));
// 使用find查找3所在位置的iterator
vector::iterator pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此处会导致非法访问
return 0;
}
这种情况与list的迭代器失效场景形成鲜明的对比,因为vector的空间连续,所以一旦出现了数据挪动的情况,其他的数据也要随之挪动,而此时若有迭代器指向被牵连的区域,那么因为迭代器指向的内容已经不是本来该指向的内容,我们认为这个迭代器也失效了。如下图:
可以看到other指向的内容已经发生了改变,所以该迭代器失效。
既然这些情况会使迭代器发生失效,那么该如何解决呢?
首先,第一个方法,便是不使用这些可能发生迭代器失效的函数,当然这显得不符合现实。
所以,第二个方法,发生迭代器失效后,在使用之前,重新对迭代器进行赋值即可。就如开文的那句话,我们让迭代器再次指向我们想要的位置即可。如面对第一种情况时,重新调用begin即可,第二种情况,删除之后,重新获取迭代器进行操作,如果是在循环中删除,觉得重新赋值太过浪费,那么可以保存删除之前未失效的迭代器,在删除之后启用未失效的迭代器即可,这其实也是一种另类的重新赋值。
至此,总的来说,小范围迭代器失效解决很容易,但大范围的迭代器失效就显得十分繁琐了。所以以此篇博客介绍vector的迭代器失效场景,望看过后在编程时不会出现迭代器失效的bug!