C++中Vector容器 -- 迭代器失效问题

情况一:扩容导致野指针

问题描述:

  • 若遇到扩容情况,会因为扩容后旧数据被放到新空间中,但pos还指向原空间对应数据的位置(而其实原空间已经被释放了),故出现了野指针 ==> 即pos失效了
  • ![[Pasted image 20220918223700.png]]

解决方式:更新pos

  • ①在扩容后的新空间再find找pos==>代价大,不推荐
  • ②通过扩容前记录的pos与_start的相对位置,重更新pos(如下)
    • ==>推荐(官方库也是这么处理的)
//解决方式②
if (_finish == _end_of_storage)
{
	size_t len = pos - _start;//扩容前记录pos和start的相对距离
	reserve(capacity() == 0 ? 4 : capacity() * 2);
	pos = _start + len;//扩容后根据新的_start和记录的相对距离,更新pos位置
}

情况二:重复使用同一个pos位置,第二次insert开始出错

错误情况①:发生扩容 ==> 野指针

问题描述:
  • 在使用pos插入一次数据后,若想再使用同一个pos,继续插入数据时,第二次插入就报错了。
  • []
思考改进:
  • 既然形参改变无法改变实参,那是否能将iterator pos改成iterator& pos不就能改变p的值吗?
    • (×)解决方式不可行,因为若采用引用,会由于出现权限的放大和缩小问题,导致出现连锁的其他错误(只有引用和指针,会导致出现权限的放大和缩小问题
    • 并且库中也没有采用iterator& pos,即是考虑到一连锁的其他问题。
    • 在这里插入图片描述
结论:
  • 总之,在pos位置插入数据以后,不要再次访问pos,因为pos可能失效了
  • 因为p是以实参的形式传入形参pos,而更新pos是在形参的形式更新的==>形参的改变无法影响实参

错误情况②:不扩容 ==> 重复指向位置已经不是原来的值了(因为数据挪动)

  • 错误描述:it找到第一个偶数后,依次向后挪动一位,再插入其2倍数后,it指向的始终是第一个偶数。
    • 在这里插入图片描述
// 如要求:在所有的偶数前面插入一个偶数2倍
vector<int> v;
v.resesrve(10);//提前预留空间,保证不出现空间不足导致扩容
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

auto it = v.begin();
while (it != v.end())//①
{
	if (*it % 2 == 0)
	{
		v.insert(it, *it * 2);
	}
	++it;
}

for (auto e : v)
{
	cout << e << " ";
}
cout << endl; //结果:1 4 4 …… 4 2 3 4
  • 解决方案:修改上方代码①while循环
while (it != v.end())//①
{
	if (*it % 2 == 0)
	{
		it=v.insert(it, *it * 2);//it在新插入元素的位置
		++it;
		++it;
	}
	else
	{
		++it;
	}
}

情况三:erase导致的迭代器失效问题

错误情况1:erase缩容==>野指针

问题描述:
  • 当erase后,进行了缩容,旧数据移动到新空间中,指向原空间的pos指针就会失效
思考改进:
  • stl只是一个规范,规定了功能有什么,但底层具体如何实现没做要求,故不同平台下实现不同。所以在erase的实现中,不保证是否存在有的编译器会进行缩容==>缩容就有可能导致出现迭代器失效的情况
    • 缩容是一种以时间换空间,但目前空间都较大,而时间很宝贵,所以不怎么会采用这样的方式。
结论:
  • 虽缩容这种方案少见,但也合理。且由于我们无法确定使用的编译器是如何实现earse的?其是否采用了earse的缩容方案?为避免这样的情况,故建议,erase之后,也不再访问pos指针

错误情况2:不涉及缩容情况,erase数据排列偶然性

  • 根据数据排列的情况,会出现错误的两种排列方式:

    • ①最后一个数是偶数==> 段错误
    • ②出现连续的偶数==>连续出现的第偶数个(如第2、4、6…个)偶数会被略过不检查
    • (即使以上两种没碰上,结果显示正确,也只是偶然性)
  • 在vs下无论哪种错误,只要it改变了指向的值,都会直接报错(如表格左);在g++下,三种各个显示不同情况

    • 在这里插入图片描述
  • 错误代码:

#include<iostream>
#include<vector>
using namespace std;

void test_vector()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
		}
		++it;
	}

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

int main()
{
	test_vector();
	return 0;
}
  • 解决方式:将上部分代码中标记①处的while循环修改如下。
    • 修改后不论何种情况都不会出错了
while (it != v.end())
{
	if (*it % 2 == 0)
	{
		it=v.erase(it);
		//删除后已经指向了删除元素的下一个位置,故不必再++
	}
	else
	{
		++it;
	}
}

总结

  • 当找到问题所在,但不知道如何解决的时候
    • ==>参考源代码,看官方是怎么解决的
  • 结论:
    • ①insert/erase的pos位置,不要直接访问pos。一定要更新,若直接访问可能各种出乎意料的结果(不论什么情况,均认为pos失效),这就是所谓迭代器失效。
      • 不同平台出错的结果可能不同,因为底层具体实现机制不同。
    • ②修改方案:接收insert/erase的值
      • insert返回值是:在新插入元素的位置
      • erase返回值是:删除的值的下一个位置

  • 整理、画图不易,希望本文能帮助大家理清迭代器失效的各个情况 ~

祝大家学习愉快 ; )

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值