【C++】vector的迭代器失效问题(什么是迭代器失效?那些操作会导致迭代器失效?如何避免迭代器失效?)

news2024/10/7 8:21:08

目录

一、前言

 二、什么是迭代器失效?

 三、哪些操作会导致迭代器失效?

四、如何避免迭代器失效?

🥝 insert迭代器失效

 ✨迭代器失效 ------ 扩容导致的野指针

 ✨迭代器失效 ------ 迭代器指向的位置意义发生改变

🍇 erase迭代器失效

 五、迭代器失效总结

六、共勉 


一、前言

        最近我们学习了 vector类用法模拟实现,同时呢也提到了C++中的迭代器失效问题,在之前的文章只是简单的提了一下,由于迭代器失效问题是非常重要的,所以特地整理出来方便后期的复习和学习。如果有老铁还不太清楚 vector类 可以看看这几篇文章:
1️⃣:  vector类的使用详解
2️⃣:  vector类的模拟实现
       这篇文章的要点只有三点:1.什么是迭代器失效?2.vector那些操作会导致迭代器失效?3.如何避免迭代器失效?

 二、什么是迭代器失效?

迭代器的作用:主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装
vector的迭代器:就是原生态指针T*

template<class T>
typedef T* iterator;                     // 迭代器某种意义上就是指针
typedef const T* const_iterator;

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

  • [1]迭代器的本质就是指针迭代器失效就是指针失效
  • [2]指针失效指针指向的空间是非法的
  • [3]指针指向非法空间:指向了被释放的空间 或者 越界访问

 三、哪些操作会导致迭代器失效?

1️⃣:所有可能会引起扩容的操作都可能会导致迭代器失效。如:resize、reserve、insert、assign、push_back等  --------------  野指针引起的迭代器失效

2️⃣:指定位置的插入和删除都会都可能会导致迭代器失效。如: insert 、erase -----------------   迭代器指向的位置意义发生改变

四、如何避免迭代器失效?

 接下来,我将会从 insert 和 erase 这两个接口函数来讲解如何避免 迭代器失效问题

🥝 insert迭代器失效

insert迭代器失效分为两类:

  • 扩容导致野指针
  • 迭代器指向的位置意义发生改变

 下面我们先给出insert的初始版本,然后再逐渐的完善

  • 注意:这里呢我们使用的是 vector模拟实现中的代码:vector类的模拟实现
void insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	//检测是否需要扩容
	if (_finish == _end_of_stoage)
	{
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
}

 ✨迭代器失效 ------ 扩容导致的野指针

  •  首先我们给出以下两组测试用例

  • 这里为什么 push_back 尾插4个后调用insert会出现随机值push_back尾插5个调用insert就没有这个问题?
  •  此问题就是迭代器失效,原因就在于pos没有更新,导致非法访问野指针

  • 上述当尾插4个数字后,再头插一个数字发生扩容。根据reserve扩容机制_start和_finish都会更新,唯独插入位置pos没有更新,此时pos依旧指向旧空间,reserve后会释放旧空间,此时的pos就是野指针,这也就导致后续执行*pos=x就是非法访问野指针
  • 解决方法

我们可以设定变量n来计算扩容前pos指针位置和_start指针位置的相对距离,最后在扩容后,让_start再加上先前算好的相对距离n就是更新后的pos指针的位置了。 

void insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	/*扩容以后pos就失效了,需要更新一下*/
	if (_finish == _end_of_stoage)
	{
		size_t n = pos - _start;//计算pos和start的相对距离
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
		pos = _start + n;//防止迭代器失效,要让pos始终指向与_start间距n的位置
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
}

 ✨迭代器失效 ------ 迭代器指向的位置意义发生改变

什么是迭代器指向的位置意义发生改变呢 ?举个例子来说明一下

  •  比如现在我要在所有的偶数前面插入2,下面我们看测试结果:
void test2()
{
	xas_vector::vector<int> v1;
	v1.Push_back(1);
	v1.Push_back(2);
	v1.Push_back(3);
	v1.Push_back(4);
	v1.Push_back(5);
	v1.Push_back(6);
	for (auto ch : v1)
	{
		cout << ch << " ";
	}
	cout << endl;

	xas_vector::vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			v1.insert(it, 20);
			it++;
		}
		it++;
	}
	for (auto ch : v1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

 这里发生了断言错误,这段代码发生了两个错误:

  1. it是指向原空间的,当insert插入要扩容时,原空间的数据拷贝到新空间上,但这也就意味着旧空间全是野指针,而it是一直指向旧空间的,随后遍历it时就非法访问野指针,也就失效了。形参的改变不会影响实参,即使你内部pos指向改变了,但是并不会影响我外部的it。
  2. 为了解决上述问题,有人觉得提前reserve开辟足够大的空间即可避免发生野指针的现象,但是又会出现一个新的问题,我们看下图:

  • 此时insert以后虽然没有扩容,it也并没有成为野指针,但是it指向位置意义变了,导致我们这个程序重复插入20。
  • 解决方法:

 给insert函数加上返回值即可解决,返回指向新插入元素的位置。

iterator insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	//检测是否需要扩容
	/*扩容以后pos就失效了,需要更新一下*/
	if (_finish == _end_of_stoage)
	{
		size_t n = pos - _start;//计算pos和start的相对距离
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
		pos = _start + n;//防止迭代器失效,要让pos始终指向与_start间距n的位置
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
	return pos;
}

 我们实际调用的时候也需要改动,让it自己接收insert后的返回值:

🍇 erase迭代器失效

 我们先给出 erase的初始版本,然后再逐渐的完善

void erase(iterator pos)
{
	//检查合法性
	assert(pos >= _start && pos < _finish);
	//从pos + 1的位置开始往前覆盖,即可完成删除pos位置的值
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;		
        it++;
	}
	_finish--;
}
  • erase的失效都是迭代器指向的位置意义发生改变,或者不在有效访问数据的有效范围内
  • erase一般不会使用缩容的方案,那么也就导致erase的失效一般不存在野指针的失效

 我们现在对下面代码进行测试:

void test_vector()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	cout << v.size() << ":" << v.capacity() << endl;
	auto it = find(v.begin(), v.end(), 2);
	if (it != v.end())
	{
		v.erase(it);
	}
	cout << *it << endl;
	*pos = 10;
	cout << *it << endl << endl;
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
}

  • 我们尾插4个数字,比较size和capacity的大小,此时是相等的,接下来删除值为2的数,此时*it就是删除数字的下一个数据也就是3,此时有效数据也少了一个,后续修改*it也就不存在问题。  
  • 如果我们这里要删除的值为4,那么结果会是怎么样的呢?

  •  我们这里一共有4个数据,按理说把最后一个数字删除以后,有效数字-1,不存在还会访问最后一个值的现象,但是此结果却是删除4以后又访问了4,而且还修改4为10,这就是典型的erase迭代器失效。 

 我们再用另外一组 例子 来测试一下:

void test_vector()
{
	//删除所有的偶数
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
    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 << " ";
	}
 
}

 画图演示错误过程: 

  •  解决方案如下:

给 erase函数 加上返回值即可解决,返回指向新插入元素的位置。 

iterator erase(iterator pos)
{
	//检查合法性
	assert(pos >= _start && pos < _finish);
	//从pos + 1的位置开始往前覆盖,即可完成删除pos位置的值
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;		
        it++;
	}
	_finish--;
	return pos;
}
void test_vector()
{
	//删除所有的偶数
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
    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)
		{
			it = v.erase(it);
		}
		else
        {
             it++;
	    }
	for (auto e : v)
	{
		cout << e << " ";
	}
 
}

 五、迭代器失效总结

vector迭代器失效有两种

  • 扩容、缩容导致野指针式失效
  • 迭代器指向的位置意义变了

系统越界机制检查,不一定能够检查到。编译实现检查机制,相对比较靠谱。

六、共勉 

      以下就是我对 vector的迭代器失效问题 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 C++ list 的理解,请持续关注我哦!!!    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1646735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

揭秘无尘布:科技与清洁的完美结合

在现代科技的浪潮下&#xff0c;人们对于清洁工作的要求越来越高&#xff0c;尤其是对于精密仪器、光学设备、电子产品等高科技产品的清洁需求更为迫切。在这样的背景下&#xff0c;无尘布作为一种特殊的清洁工具&#xff0c;备受关注并得到广泛应用。本文将深入揭秘无尘布的科…

怎么口语外教一对一课程?这篇文章告诉你答案!

怎么口语外教一对一课程&#xff1f;在当今全球化的时代&#xff0c;英语口语能力已经成为许多人追求的重要技能。为了满足这一需求&#xff0c;市场上涌现出了许多提供一对一口语外教课程的软件。这些软件不仅提供了与母语为英语的外教进行实时交流的机会&#xff0c;还通过互…

德国韦纳WENAROLL滚压刀,液压缸,滚光刀,挤压刀,滚轧刀

德国韦纳WENAROLL滚压刀,液压缸&#xff0c;滚光刀,挤压刀&#xff0c;滚轧刀&#xff08;百度一下&#xff0c;西安尚融&#xff09; 德国韦纳&#xff08;WENAROLL&#xff09;的滚压刀、液压缸、滚光刀、挤压刀和滚轧刀在工业领域享有很高的声誉&#xff0c;这些产品因其高…

【深度学习实战(27)】「分布式训练」DDP单机多卡并行指南

一、DDP实现流程 &#xff08;1&#xff09;初始化进程 &#xff08;2&#xff09;model并行 &#xff08;3&#xff09;BN并行 &#xff08;4&#xff09;data并行 &#xff08;5&#xff09;进程同步 二、DDP代码实现 &#xff08;1&#xff09;初始化进程 #------------…

vivado Virtex 和 Kintex UltraScale+ 比特流设置

下表所示 Virtex 和 Kintex UltraScale 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

Vue 基础语法

【1】模板语法 &#xff08;1&#xff09;差值表达式 {{}}是 Vue.js 中的文本插值表达式。 它用于在模板中输出数据或表达式的值。当数据或表达式的值发生变化时&#xff0c;插值表达式会自动更新。 补充&#xff1a;三目运算符 它的基本语法是 Condition ? A : B&#xff0…

python绘图(pandas)

matplotlib绘图 import pandas as pd abs_path rF:\Python\learn\python附件\pythonCsv\data.csv df pd.read_csv(abs_path, encodinggbk) # apply根据多列生成新的一个列的操作&#xff0c;用apply df[new_score] df.apply(lambda x : x.数学 x.语文, axis1)# 最后几行 …

3W 3KVDC 隔离单输出 DC/DC 电源模块——TPG-3W 系列

TPG-3W系列是一款额定功率为3W的隔离产品&#xff0c;国际标准引脚&#xff0c;宽范围工作、温度–40℃ 到 105℃&#xff0c;在此温度范围内都可以稳定输出3W&#xff0c;并且效率非常高&#xff0c;高达88%&#xff0c;同时负载调整率非常低&#xff0c;对于有输出电压精度有…

61-ARM与FPGA间SPI通信电路设计

视频链接 ARM与FPGA间SPI通信电路设计01_哔哩哔哩_bilibili ARM与FPGA间SPI通信电路设计 第22课&#xff1a;SPI Flash 电路设计 第65课&#xff1a;实战S1-FPGA板级实战导学 1、SPI简介 1.1、SPI概述 SPI是(Serial Peripheral Interface) 串行外设接口&#xff0c;由Mot…

产业空间集聚DO指数计算

1.前言 创始人 :Duranton and Overman&#xff08;2005&#xff09; 目前应用较多的产业集聚度量指数主要基于两类&#xff0c;一是根据不同空间地理单元中产业经济规模的均衡性进行构造&#xff0c;如空间基尼系数与EG指数&#xff1b;二是基于微观企业地理位置信息形成的产业…

求解亲和数

【问题描述】 古希腊数学家毕达哥拉斯在自然数研究中发现&#xff0c;220的所有真约数&#xff08;即不是自身 的约数&#xff09;之和为&#xff1a; 1245101120224455110284。而284的所有真约数为1、2、4、71、142&#xff0c;加起来恰好为220。人 们对这样的数感到很惊奇&am…

H3C ripng实验(ipv6)

H3C ripng实验&#xff08;ipv6&#xff09; 实验需求 按照图示为路由器配置IPv6地址 所有路由器运行ripng&#xff0c;进行ipv6网段的互通 查询路由表后&#xff0c;​进行全网段的ping测试&#xff0c;实验目的RTD可以ping通RTA 实验解法 按照图示为路由器配置IPv6地址 …

力扣295. 数据流的中位数

Problem: 295. 数据流的中位数 文章目录 题目描述思路复杂度Code 题目描述 思路 1.定义一个大顶堆和小顶堆&#xff1b; 2.当添加的数据小于大顶堆的堆顶元素或者大顶堆为空时&#xff0c;将元素添加到大顶堆&#xff1b;当元素大于大顶堆堆顶元素时添加到小顶堆&#xff1b;同…

easy_signin_ctfshow_2023愚人杯

https://ctf.show/challenges#easy_signin-3967 2023愚人杯信息检索&#xff0c;在请求荷载中发现一个base64 face.pngencode ZmFjZS5wbmc解密结果 flag.pngencode ZmxhZy5wbmc尝试一下 返回内容 Warning: file_get_contents(flag.png): failed to open stream: No such file…

T0策略是什么?有哪些优点和缺点,如何操作T0策略?

T0策略又称日内交易策略&#xff0c;它的持仓时间较短&#xff0c;基于对未来短期股价走势的判断&#xff0c;通过低位买入、高位卖出的方式来获得价差收益&#xff0c;并且买入卖出交易在日内完成。 T0策略分类 按照策略逻辑分类&#xff0c;T0策略可分为融券T0和底仓T0。融…

Android11 InputManagerService启动流程分析

InputManagerService在systemserver进程中被启动 //frameworks\base\services\java\com\android\server\SystemServer.java t.traceBegin("StartInputManagerService"); inputManager new InputManagerService(context);//1 t.traceEnd(); //省略 //注册服务 Servi…

我独自升级崛起在哪下载 我独自升级电脑PC端下载教程分享

将于5月8日在全球舞台闪亮登场的动作角色扮演游戏《我独自升级崛起》&#xff0c;灵感源自同名热门动画与网络漫画&#xff0c;承诺为充满激情的游戏玩家群体带来一场集深度探索与广阔体验于一身的奇幻旅程。该游戏以独特的网络武侠世界观为基底&#xff0c;展现了一位普通人踏…

React + 项目(从基础到实战) -- 第11期

目标 问卷编辑器的开发 设计UI - 拆分布局 水平垂直居中 画布 y方向滚动 自定义问卷组件 后端 返回组件数据 //获取单个问卷信息{url: /api/question/:id,method: get,response: () > {return {errno: 0,data: {id: Random.id(),title: Random.ctitle(),componentList:[//…

纯血鸿蒙APP实战开发——页面间共享组件实例的案例

介绍 本示例提供组件实例在页面间共享的解决方案&#xff1a;通过Stack容器&#xff0c;下层放地图组件&#xff0c;上层放Navigation组件来管理页面&#xff0c;页面可以共享下层的地图组件&#xff0c;页面中需要显示地图的区域设置为透明&#xff0c;并参考触摸交互控制&am…

Linux网络-部署YUM仓库及NFS共享服务

目录 一.YUM仓库服务 1.YUM概述 1.1.YUM&#xff08;Yellow dog Updater Modified&#xff09; 2.准备安装源 2.1.软件仓库的提供方式 2.2.RPM软件包的来源 2.3.构建CentOS 7 软件仓库 2.4.在软件仓库中加入非官方RPM包组 3.一键安装软件包的工具&#xff1a; 好处&a…