【C++进阶】深入STL之vector:深入研究迭代器失效及拷贝问题

news2024/10/4 20:22:45

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:初步了解vector
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀STL之vector

  • 📒1. 迭代器失效
    • 🌈插入时失效
    • 🌞删除时失效
  • 📕2. 解决迭代器失效
    • 🍂在插入时失效
    • 🍁在删除时失效
  • 📜3. vector的拷贝问题
    • 🎩浅拷贝
    • 🎈深拷贝
  • 📖4. 总结补充
    • 💧补充:insert和erase的模拟实现(优化前)
    • 🔥总结


前言:在C++的STL(Standard Template Library)库中,vector容器无疑是最常用且功能强大的数据结构之一。它提供了动态数组的功能,允许我们在运行时动态地增加或减少元素。然而,随着我们对vector的深入使用,一些潜在的问题也逐渐浮现,其中最为常见和棘手的就是迭代器失效以及拷贝问题 (关于初始inserterase的模拟实现在本篇末尾)


注意:我们使用的函数是上一篇模拟实现的函数

📒1. 迭代器失效

迭代器失效是指在使用迭代器遍历或操作vector容器时,由于某些操作导致迭代器失效,无法再正确引用容器中的元素。 这种情况往往发生在vector容器进行扩容、插入或删除元素等操作时。迭代器失效可能导致程序出现未定义行为,甚至崩溃。

因此:深入理解vector迭代器失效的原因和场景,对于编写健壮、可靠的C++代码至关重要。


🌈插入时失效

代码示例:(插入)

void test_vector()
{
	vector<int> v1; // 创建一个vector插入4个元素
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	vector<int>::iterator it = find(v1.begin(), v1.end(), 1);
	v1.insert(it, 2); // 然后我们再来插入两个元素
	v1.insert(it, 3); 
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

哎呀,怎么程序出错了?
在这里插入图片描述

扩容前:迭代器pos在_start和_finish之间
扩容后:start和finish的地址改变,pos不再指向vector区域的位置

迭代器失效: 迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间


🌞删除时失效

erase也会造成迭代器失效
代码示例:(删除)

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);
	v.push_back(6);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) v.erase(it);
		++it;
	}
}

此段代码依然会出现错误,我们可以画图来理解:
在这里插入图片描述

erase删除元素后,会进行数据的挪动,我们自己也对迭代器进行了++,导致最后it指向了vector有效范围之外

注意:在vs中,使用erase函数,因为vs对迭代器进行了封装,编译器自动认为此位置迭代器失效


📕2. 解决迭代器失效

迭代器失效解决办法:在使用前,对迭代器重新赋值即可


🍂在插入时失效

这种情景是因为在插入一次元素时,进行了扩容,导致pos位置不对,因此我们只需要不用当前pos迭代器,而是将pos指向进行更新,但是这样做依然解决不了迭代器失效,我们参考库里面,是将insertvoid变成iterator 类型,将迭代器返回给it重新赋值即可

iterator insert(iterator pos, const T& x)
{ 
	assert(pos <= _finish);
	assert(pos >= _start);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start; // 在扩容时, 我们保留下pos和_start的相对位置
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len; // 在扩容结束后,将pos恢复回来
		// 虽然我们进行了此处操作当时依然不能避免迭代器失效
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = x;
	_finish++;
	return pos; // 返回迭代器在重新赋值
}

🍁在删除时失效

解决删除时的迭代器失效,我们只需要更改代码,让它删除后不用再++迭代器,或者没删除的时候再++,但是这样治标不治本,因此我们选择效仿库里面,返回迭代器,将迭代器返回给it重新赋值即可


iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	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(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) it = v.erase(it);
		else ++it;
	}
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可


📜3. vector的拷贝问题

vector的拷贝问题也是我们在实际编程中经常需要面对的挑战。拷贝操作在C++中非常常见,无论是函数参数的传递、对象的赋值还是容器之间的交互,都可能涉及到拷贝操作。然而,对于vector这样的动态容器,拷贝操作可能会带来性能上的开销,尤其是浅拷贝和深拷贝的问题,容易给我们带来困扰


🎩浅拷贝

由于我们在模拟实现时,用的都是memcpy来拷贝元素,操作不慎就会引发浅拷贝问题

  • memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  • 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
// memcpy(tmp, _start, sizeof(T) * sz); 拷贝元素

void test_vector()
{
	vector<string> v1;
	v1.push_back("aaaaaaaaaaaaaa");
	v1.push_back("bbbbbbbbbbbbbb");
	v1.push_back("cccccccccccccc");
	v1.push_back("dddddddddddddd");
	v1.push_back("dddddddddddddd");
	v1.push_back("eeeeeeeeeeeeee"); // 此处需要扩容 
	for (auto e : v1)
	{
		cout << e << " ";
	}
}

在这里插入图片描述
memcpy会带来浅拷贝的隐患,因此我们用另外一种方法来进行拷贝

结论: 如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。


🎈深拷贝

我们可以用for循环将memcpy进行替换来避免浅拷贝,造成程序崩溃

void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		size_t sz = size();
		size_t cp = capacity();
		T* tmp = new T[cp];

		//memcpy(tmp, _start, sizeof(T) * sz);
		// 用for循环进行深拷贝
		for (size_t i = 0; i < sz; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;

		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + cp;
	}
	*_finish = x;
	_finish++;
}

📖4. 总结补充

💧补充:insert和erase的模拟实现(优化前)

void insert(iterator pos, const T& x)
{ 
	assert(pos <= _finish);
	assert(pos >= _start);
	
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = x;
	_finish++;
}

void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);
	
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it-1) = *it;
		it++;
	}
	_finish--;
}

🔥总结

在深入探讨STL中vector的迭代器失效和拷贝问题后,我们不难发现,这些问题虽然常见,但理解其背后的原理并采取相应的措施,可以有效避免它们带来的潜在风险

  • 对于迭代器失效,我们了解到它通常发生在vector进行扩容、插入或删除元素等操作时。为了避免迭代器失效,我们需要时刻注意迭代器的有效性和生命周期,确保在操作过程中不会意外地修改或销毁迭代器所指向的对象。此外,了解vector扩容的时机和机制,也可以帮助我们预测和避免潜在的迭代器失效问题
  • 而对于拷贝问题,我们认识到vector的拷贝操作可能会带来性能上的开销,以及造成程序崩溃的结果。为了减少这些开销,我们可以考虑使用移动语义、避免不必要的拷贝以及优化拷贝策略等方法。同时,了解不同拷贝方式的优缺点和适用场景,可以帮助我们更加明智地选择适当的拷贝方式

我们希望能够为大家提供关于vector迭代器失效和拷贝问题的深入理解,并引导他们采取正确的措施来避免这些问题。然而,学习是一个永无止境的过程。随着C++语言的不断发展和STL库的更新迭代,我们可能会发现更多关于vector的新特性和最佳实践。 因此,我们希望大家继续深入学习C++和STL的相关知识,不断提高自己的编程能力和代码质量

在这里插入图片描述

谢谢大家支持本篇到这里就结束了,祝大家天天开心!
在这里插入图片描述

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

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

相关文章

【Java基础】线程的五大状态

新建状态 使用 new 关键字和 Thread 类或其子类建立一个线程对象后&#xff0c;该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 就绪状态 当线程对象调用了start()方法之后&#xff0c;该线程就进入就绪状态。就绪状态的线程处于就绪队列中&#xff…

【机器学习基础】Python编程04:五个实用练习题的解析与总结

Python是一种广泛使用的高级编程语言,它在机器学习领域中的重要性主要体现在以下几个方面: 简洁易学:Python语法简洁清晰,易于学习,使得初学者能够快速上手机器学习项目。 丰富的库支持:Python拥有大量的机器学习库,如scikit-learn、TensorFlow、Keras和PyTorch等,这些…

(学习笔记)数仓建模

数仓建模 OLAP数仓分层数据模型数据模型建设方法模型建设具体流程模型数据域事实表设计事实表拉链表 数据模型规范表命名(采用阿里one-data设计)字段命名(采用阿里one-data设计)数据模型标注规范 数据模型发展周期 OLAP OLTP&#xff1a;概念全称OnLine Transaction Processin…

【大模型】Ollama+open-webui/Anything LLM部署本地大模型构建RAG个人知识库教程(Mac)

目录 一、Ollama是什么&#xff1f; 二、如何在Mac上安装Ollama 1. 准备工作 2. 下载并安装Ollama 3. 运行Ollama 4. 安装和配置大型语言模型 5. 使用Ollama 三、安装open-webui 1. 准备工作 2. Open WebUI ⭐的主要特点 3. Docker安装OpenWebUI&#xff0c;拉去太慢…

操作系统教材第6版——个人笔记3

2.1 处理器 2.1.1 处理器与寄存器 处理器部件的简单示意 用户程序可见寄存器 可以使程序员减少访问主存储器的次数&#xff0c;提高指令执行的效率所有程序可使用&#xff0c;包括应用程序和系统程序数据寄存器&#xff1a;又称通用寄存器地址寄存器&#xff1a;索引、栈指针…

妙手ERP接入TEMU美国本土店,支持高效产品刊登、订单管理、库存管理

​​​​​​​ 众所周知&#xff0c;拼多多向来是低价卷王&#xff0c;而TEMU完美继承了拼多多的基因&#xff0c;靠着全托管模式一路狂奔&#xff0c;两年多便扩展至全球60个国家与地区&#xff0c;市场规模预计达到450亿美元&#xff0c;吸引着越来越多的卖家入驻。 在平台…

32C3-2模组与乐鑫ESP32­-C3­-WROOM­-02模组原理图、升级口说明

模组原理图&#xff1a; 底板原理图&#xff1a; u1 是AT通信口&#xff0c;wiif-tx wifi-rx 是升级口&#xff0c;chip-pu是reset复位口&#xff0c;GPIO9拉低复位进入下载模式 ESP32-WROOM-32 系列硬件连接管脚分配 功能 ESP32 开发板/模组管脚 其它设备管脚 下载固件…

Unity DOTS技术(八)状态组件

文章目录 一,简介二.实例 一,简介 因为在ECS中没有回调,因而无从得知组件是否被销毁.所以我们需要使用ISystemStateComponentData. 什么时ISystemStateComponentData状态组件? 当物体被销毁时ISystemStateComponentData组件不会被销毁.因而我们可以在组件被销毁时在该组件中…

搭建大型分布式服务(三十八)SpringBoot 整合多个kafka数据源-支持protobuf

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

Clo3D导出服装动画,使用Unity3D展示

1.前言 Clo3D是一款应用于时装行业的3D服装设计软件,其强大的布料模拟算法可在3D空间中实现设计、制版、试衣和走秀,大幅提升数字作品逼真度和制作效率。为了让服装动画效果展示在Unity3D上模拟效果&#xff0c;需要Clo3D模拟出逼着的衣服动画。总体流程为Clo3D - Mixamo -Blen…

废酸再生工艺的稳定性强优势

废酸再生工艺&#xff0c;作为现代工业中一项重要的环保技术&#xff0c;其核心目的是将工业生产过程中产生的废酸进行回收、处理和再利用&#xff0c;以实现资源的节约和环境的保护。这一工艺不仅有助于减少废酸对环境的污染&#xff0c;还能为企业带来显著的经济效益。 一、废…

idea实用快捷键(持续更新...)

文章目录 1、快速输入try/catch/finally2、选中多个光标3、实现接口4、方法参数提示5、查看某个类的子类6、弹出显示查找内容的搜索框 1、快速输入try/catch/finally CtrlAltT 2、选中多个光标 ShiftAlt单机多选 End可以全部到行尾&#xff0c;Home则可以全部回到行首 3、实现接…

MySQL的增删改查2

文章目录 1. 数据库约束1.1 约束类型1.2 NOT NULL约束1.3 UNIQUE唯一约束1.4 DEFAULT默认值约束1.5 PRIMARY KEY主键约束1.6 FOREIGN KEY外键约束1.7 CHECK约束 2. 新增3. 查询3.1 聚合查询3.1.1 聚合函数3.1.2 GROUP BY子句3.1.3 HAVING 3.2 联合查询3.2.1 内连接3.2.2 外连接…

慧天卓特:2024年“一带一路”之哈萨克斯坦旱情监测案例分析(FYDI)

引言 联合国防治荒漠化公约组织指出&#xff1a;中亚约有1200万人生活在干旱风险高的地区&#xff0c;面积约为4000万公顷。位于亚洲中部的哈萨克斯坦共和国&#xff08;简称哈萨克斯坦&#xff09;和中国有着长期友好的睦邻和经贸关系&#xff0c;中国是哈萨克斯坦的主要农产…

C++:day3

思维导图 练习题 #include <iostream> using namespace std;class Per { private:string name;int age;int *height;double weight;public:Per(){cout << "Per::无参构造函数" << endl;}Per(string name, int age, int height, double weight) :…

2004NOIP普及组真题 2. 花生采摘

线上OJ&#xff1a; 【04NOIP普及组】花生采摘 核心思想&#xff1a; 1、本题为贪心即可。 2、因为本题严格限制了顺序&#xff0c;所以先把每个节点的花生数量按降序排序。然后逐一判断下一个花生是否需要去采摘即可 3、每一次采摘完&#xff0c;记录耗时 t 以及采集的花…

GPT-4o(OpenAI最新推出的大模型)

简介&#xff1a;最近&#xff0c;GPT-4o横空出世。对GPT-4o这一人工智能技术进行评价&#xff0c;包括版本间的对比分析、GPT-4o的技术能力以及个人感受等。 方向一&#xff1a;对比分析 GPT-4o&#xff08;OpenAI最新推出的大模型&#xff09;与GPT-4之间的主要区别体现在响应…

SSM框架整合,内嵌Tomcat。基于注解的方式集成

介绍&#xff1a; SSM相信大家都不陌生&#xff0c;在spring boot出现之前&#xff0c;SSM一直是Java在web开发中的老大哥。现在虽说有了spring boot能自动整合第三方框架了&#xff0c;但是现在市面上任然有很多老项目是基于SSM技术的。因此&#xff0c;能熟练掌握SSM进行开发…

Python接入淘宝API接口采集商品详情页到手价优惠券信息数据:智能化营销的加速器

在电子商务领域&#xff0c;智能化营销正在成为提高效率和竞争力的关键。淘宝API提供了一套完整的解决方案&#xff0c;帮助商家实现智能化营销&#xff0c;从而提升销售业绩和顾客满意度。 库存管理&#xff1a; 淘宝API使商家能够实时监控库存水平&#xff0c;自动补货&#…

【康耐视国产案例】智能AI相机:深度解析DataMan 380大视野高速AI读码硬实力

随着读码器技术的不断更新迭代&#xff0c;大视野高速应用成为当前工业读码领域的关键发展方向。客户对大视野高速读码器的需求源于其能显著减少生产成本并提升工作效率。然而&#xff0c;大视野应用场景往往伴随着对多个条码的读取需求&#xff0c;这无疑增加了算法的处理负担…