C++——vector迭代器失效问题

迭代器失效问题

  • 前言
  • 为什么会发生迭代器失效
  • 引发迭代器失效的操作
  • 总结


前言

本节主要讲述的是vector的迭代器失效问题,这个问题是大家比较不容易注意到的,有时候要是没有仔细的观察就有可能犯这种错误。

为什么会发生迭代器失效

首先,我们要了解迭代器的主要作用就是让算法不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装(通过重载运算符让其达到和指针一样的效果),例如:vector的迭代器就是原生指针T*,而list的迭代器就是封装过的指针。

迭代器失效,实际就是迭代器底层对应指针所指向的空间已经被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

引发迭代器失效的操作

对于vector来说可能导致迭代器失效的操作有:

  1. 会引起其底层空间改变的操作,都有可能导致迭代器失效
    例如: resize,reserve, insert, assign, push_back等
#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迭代器内保存的依旧是扩容前的地址,该地址上的空间已经失效了无法继续使用,继续使用是会报错的。

  1. erase操作也可能导致迭代器失效

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要访问一个野指针是不被允许的,所以程序报错!

这段代码不仅在最后一个数是偶数时会出现问题,有连续的偶数时也会,如下:
C++——vector迭代器失效问题_第1张图片
在这种情况下,连续的两个偶数只会删去一个。

那么,如果根据上面的理解,修改成下面的代码,是否可行呢?

#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++编译器下也确实能成功运行。
C++——vector迭代器失效问题_第2张图片

但是如果在vs下运行呢?
C++——vector迭代器失效问题_第3张图片
我们发现在vs下甚至无法正常运行,这是由于c++标准并没有规定使用失效的迭代器的后果,所以两款编译器的实行结果是不一样的,vs对其进行了强制检查,不允许使用失效的迭代器,而linux下g++对迭代器的检测并不是非常严格,处理也没有vs下极端。

所以,上面的代码没有可移植性,也是一个错误的代码,那么,该如何解决这个问题呢?
我们看看文档中的函数声明:
C++——vector迭代器失效问题_第4张图片

C++——vector迭代器失效问题_第5张图片

从该声明中我门可以发现,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; 
}

上面的代码在vs下也可以正常运行了!
C++——vector迭代器失效问题_第6张图片

总结

迭代器失效问题是我们有可能会忽略的一个问题,不仅仅是vector有这个问题,所有stl容器和string都有可能会出现这一问题,有时候会给我们造成很大麻烦,所以大家在平时写代码时一定要注意这一问题!

C++——vector迭代器失效问题_第7张图片

你可能感兴趣的:(C++,c++,开发语言)