本节主要讲述的是vector的迭代器失效问题,这个问题是大家比较不容易注意到的,有时候要是没有仔细的观察就有可能犯这种错误。
首先,我们要了解迭代器的主要作用就是让算法不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装(通过重载运算符让其达到和指针一样的效果),例如:vector的迭代器就是原生指针T*,而list的迭代器就是封装过的指针。
迭代器失效,实际就是迭代器底层对应指针所指向的空间已经被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector来说可能导致迭代器失效的操作有:
#include
using namespace std;
#include
int main()
{
vector<int> 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(8);
// 给vector重新赋值,可能会引起底层容量改变
v.assign(100, 8);
//进行了如上操作后,it迭代器已经失效,不能继续使用
/*while(it != v.end())
{
cout<< *it << " " ;
++it;
}*/
cout<<endl;
return 0;
}
这些操作可能导致vector失效的根本原因就是以上操作都有可能导致vector扩容,而对于 vector来说,其的扩容在底层所作的事就是释放当前指针指向的空间,然后再
new
一个更大的空间(相当于是进行了异地扩容的操作),那么,其数据存放的地址就不是在原来的位置了,对于上面的例子来说,it
迭代器内保存的依旧是扩容前的地址,该地址上的空间已经失效了无法继续使用,继续使用是会报错的。
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。
有了上面的基础,我们来看看下面这个给删除vector中所有偶数的代码是否正确:
#include
using namespace std;
#include
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0;
}
答案当然是错误的,那么为什么呢?看最后一步,当it走到4之后,进行erase操作导致vector内部指向结尾的指针–,此时end()已经等于it了,但是程序没有结束,it继续++,导致it指向end()的下一个位置,从而使得结束条件不成立继续循环,而*it要访问一个野指针是不被允许的,所以程序报错!
这段代码不仅在最后一个数是偶数时会出现问题,有连续的偶数时也会,如下:
在这种情况下,连续的两个偶数只会删去一个。
那么,如果根据上面的理解,修改成下面的代码,是否可行呢?
#include
using namespace std;
#include
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
else
++it;
}
return 0;
}
逻辑上确实没有问题,在linux的g++编译器下也确实能成功运行。
但是如果在vs下运行呢?
我们发现在vs下甚至无法正常运行,这是由于c++标准并没有规定使用失效的迭代器的后果,所以两款编译器的实行结果是不一样的,vs对其进行了强制检查,不允许使用失效的迭代器,而linux下g++对迭代器的检测并不是非常严格,处理也没有vs下极端。
所以,上面的代码没有可移植性,也是一个错误的代码,那么,该如何解决这个问题呢?
我们看看文档中的函数声明:
从该声明中我门可以发现,erase函数有一个返回值,根据文档,这个返回值指向的是被删除元素的下一个元素的迭代器,不难发现insert函数也有这一返回值,那么我们就可以利用这一返回值来解决迭代器失效问题了!
最终代码如下:
#include
using namespace std;
#include
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
++it;
}
return 0;
}
迭代器失效问题是我们有可能会忽略的一个问题,不仅仅是vector有这个问题,所有stl容器和string都有可能会出现这一问题,有时候会给我们造成很大麻烦,所以大家在平时写代码时一定要注意这一问题!