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

📝个人主页🌹: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的相关知识,不断提高自己的编程能力和代码质量

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

聚焦热点-“十五五”规划 国家发改委前期研究课题汇总

聚焦热点-“十五五”规划 国家发改委前期研究课题汇总 随着“十五五”规划的脚步日益临近&#xff0c;国家发改委及地方相关机构已启动了前期研究工作&#xff0c;以确保地方规划能够准确把握时代脉搏&#xff0c;推动经济社会的高质量发展。 2023年12月17日至18日&#xff0…

10.爬虫---XPath插件安装并解析爬取数据

10.XPath插件安装并解析爬取数据 1.XPath简介2.XPath helper安装3.XPath 常用规则4.实例引入4.1 //匹配所有节点4.2 / 或 // 匹配子节点或子孙节点4.3 ..或 parent::匹配父节点4.4 匹配属性4.5 text()文本获取4.6 属性获取4.7 属性多值匹配 1.XPath简介 XPath是一门在XML文档中…

【WP】猿人学13_入门级cookie

https://match.yuanrenxue.cn/match/13 抓包分析 抓包分析发现加密参数是cookie中有一个yuanrenxue_cookie 当cookie过期的时候&#xff0c;就会重新给match/13发包&#xff0c;这个包返回一段js代码&#xff0c;应该是生成cookie的 <script>document.cookie(y)(u)(a…

RocketMQ可视化界面安装

RocketMQ可视化界面安装 **起因&#xff1a;**访问rocketmq-externals项目的git地址&#xff0c;下载了源码&#xff0c;在目录中并没有找到rocketmq-console文件夹。 git下面文档提示rocketMQ的仪表板转移到了新的项目中&#xff0c;点击仪表板到新项目地址&#xff1b; 下载…

flutter封装日历选择器(单日选择)

简单封装&#xff1a; 引入库&#xff1a;table_calendar import package:generated/l10n.dart; import package:jade/utils/JadeColors.dart; import package:jade/utils/Utils.dart; import package:util/easy_loading_util.dart; import package:flutter/material.dart; im…

【Python报错】已解决ModuleNotFoundError: No module named ‘gensim’

成功解决“ModuleNotFoundError: No module named ‘gensim’”错误的全面指南 在Python编程中&#xff0c;尤其是进行文本挖掘和自然语言处理&#xff08;NLP&#xff09;时&#xff0c;gensim库是一个常用的工具&#xff0c;用于主题建模、文档相似度计算、词向量表示&#x…

泽众云真机-上线海外机型测试专栏

泽众云真机平台&#xff0c;2024上半年70机型升级&#xff0c;也包括热门的海外机型。 但是&#xff0c;运营客服反馈&#xff0c;用户找不到平台海外机型在哪里&#xff0c;我们发现海外机型排列位置有问题&#xff0c;用户不易发现。目前问题已解决&#xff0c;上线海外机型测…

应对800G以太网挑战:数据中心迁移

在过去几年中&#xff0c;云基础设施和服务的大规模使用推动了对更多带宽、更快速度和更低延迟性能的需求。交换机和服务器技术的改进要求布线和架构随之调整。因此&#xff0c;800G以太网对数据中心迁移的需求&#xff0c;特别是对速率&#xff08;包括带宽、光纤密度和通道速…

MySQL学习——选项文件的使用

MySQL 的许多程序都可以从选项文件&#xff08;有时也被称为配置文件&#xff09;中读取启动选项。选项文件提供了一种方便的方式来指定常用的选项&#xff0c;这样你就不必每次运行程序时都在命令行上输入这些选项。 要确定一个程序是否读取选项文件&#xff0c;你可以使用 -…

搭建高可用k8s

高可用只针对于api-server&#xff0c;需要用到nginx keepalived&#xff0c;nginx提供4层负载&#xff0c;keepalived提供vip(虚拟IP) 系统采用openEuler 22.03 LTS 1. 前期准备 因为机器内存只有16G&#xff0c;所有我采用3master 1node 1.1 修改主机配置&#xff08;所有节…

单投币的充电桩如何加装一个扫码模块

充电桩需要投币才能充电&#xff0c;可是现在的人们很少有带硬币的习惯&#xff0c;扫码成为了一个常规的手段。我们也会发现有的充电桩无法扫码&#xff0c;或者说扫码无效&#xff0c;那是因为充电桩没有安装扫码模块&#xff0c;那么充电桩该如何加装扫码模块。 首先将充电桩…

Podman和Docker的区别

Podman 和 Docker 都是用于容器化的工具&#xff0c;但它们在架构、安全性、容器编排以及一些设计理念上有显著的区别&#xff1a; 架构设计: Docker 使用客户端-服务器&#xff08;C/S&#xff09;架构&#xff0c;包含一个名为 dockerd 的守护进程&#xff0c;该进程以 root …

西门子学习笔记6 - TCP通讯

1、主站设置 1、添加两个PLC在网络组态进行链接在一起&#xff0c;使用tcp链接 2、设置主站IP地址为&#xff1a;192.168.1.1 3、添加TSEND_C功能块 4、设置功能块参数连接 5、设置如下所示&#xff08;连接参数设置&#xff09; 6、设置如下所示&#xff08;连接块参数设置&a…

【Text2SQL】评估 LLM 的 Text2SQL 能力

论文&#xff1a;Evaluating the Text-to-SQL Capabilities of Large Language Models ⭐⭐⭐⭐ arXiv:2204.00498 一、论文速读 本论文尝试了多种 prompt 结构&#xff0c;并且评估了他们在 Codex 和 GPT-3 上的表现。下面介绍这些 prompt 结构&#xff1a; 二、不同的 prom…

UI 自动化中的分层设计

以前的设计 在过去 UI 自动化测试领域有一个规范的设计模式是 page object 模式。 意思是测试用例不会直接定位页面元素&#xff0c; 而是把每一个页面封装成一个类。 在这个类中封装页面元素。 然后测试用例调用 page 类来操作页面元素完成测试用例。如下图&#xff1a; 以前…

Linuxftp服务001匿名登入

在Linux系统中搭建FTP&#xff08;File Transfer Protocol&#xff09;服务&#xff0c;可以让用户通过网络在服务器与其他客户端之间传输文件。它有几种登入模式&#xff0c;今天我们讲一下匿名登入。 操作系统 CentOS Stream9 操作步骤 首先我们先下载ftp [rootlocalhost…

刷爆leetcode第八期

题目一 设计循环队列 题目分析 这里直接看图 我们发现这里要求我们设计一个循环队列 这要怎么设计呢&#xff1f; 还是一样 我们先画图 我们首先假设只能储存四个数字 同学们看这张图能观察到什么呢&#xff1f; 是不是可以得到front 和 rear相等的时候整个队列为空 这里…

【微机原理及接口技术】中断系统

【微机原理及接口技术】中断系统 文章目录 【微机原理及接口技术】中断系统前言一、中断概述中断的基本概念中断处理过程 二、8086/8088中断系统中断类型中断响应过程中断向量表内部中断服务程序 总结 前言 本篇文章我们会讲到中断的概述&#xff0c;8086/8088中断系统。 一、…

Mysql疑难报错排查 - Field ‘XXX‘ doesn‘t have a default value

项目场景&#xff1a; 数据库环境 &#xff1a;mysql8; 工程使用&#xff1a;MyBatisPlus 表情况&#xff1a; 问题描述 某一个插入语句使用了 MyBatisPlus 的 save 方法&#xff0c;因为end_time1 end_time2都并没有值&#xff0c;所以在MyBatisPlus默认情况下&#xff0c;…

SQL优化系列-快速学会分析SQL执行效率(下)

1 show profile 分析慢查询 有时需要确定 SQL 到底慢在哪个环节&#xff0c;此时 explain 可能不好确定。在 MySQL 数据库中&#xff0c;通过 profile&#xff0c;能够更清楚地了解 SQL 执行过程的资源使用情况&#xff0c;能让我们知道到底慢在哪个环节。 知识扩展&#xff1…