【C++进阶(三)】STL大法--vector迭代器失效深浅拷贝问题剖析

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述

vector-下

  • 1. 前言
  • 2. 什么是迭代器失效?
  • 3. 迭代器失效的经典案例
  • 4. 迭代器失效的解决方案
  • 5. 对于reverse的深度剖析
  • 6. vector深浅拷贝问题
  • 7. vector深浅拷贝的解决方法
  • 8. 总结以及拓展

1. 前言

在阅读本篇文章前,一定要先看前集:

vector深度剖析(上)

本章重点:

本章会重点讲解vector迭代器失效问题
以及vector中的深浅拷贝问题
并且简单完善一下vector的自我实现

在此之前,我将在文章末尾把vector
自我实现的完整代码分享给大家


2. 什么是迭代器失效?

首先我们要清楚一点:
vector的每一次扩容都不是在
原地扩容,而是新开辟一块儿空间后
将原先的数据拷贝到新空间

请看下面的代码:

vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto pos = find(v.begin(),v.end(),3);
v.insert(pos,30);
v.insert(pos,40);

这段代码在3前面插入一个30和40
但是这段代码会出错!

为什么呢?请看下图:

在这里插入图片描述

注:从四个数据插入为五个会扩容

  • 扩容前
    迭代器pos在start和finish之间
  • 扩容后
    start和finish的地址改变,pos失效
    pos不再指向现在的位置3

迭代器失效的本质原因是:
扩容后start和finish的地址发生变化
指向原先位置的迭代器统统失效!

若没发生扩容,则一切安好!


3. 迭代器失效的经典案例

除了前面讲到的insert导致迭代器失效外
erase函数也会导致迭代器失效

请看下面的案例:

vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(6);

for (auto e : v)
{
	cout << e << " ";
}
cout << endl;
auto it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 0)
	{
		it = v.erase(it);
	}
	++it;
}

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

这段代码在删除顺序表中所有的偶数
但是你会发现它并没有删除完
这是为啥呢?请看下图的分析

在这里插入图片描述

erase删除后,后面的数据会覆盖过来
此时不让迭代器++它也指向下一个位置


4. 迭代器失效的解决方案

  1. 对于insert来说

在pos位置使用一次insert后
不要再次直接访问pos迭代器
一定要更新了pos之后再去访问!

库中的vector提供了返回值来解决此问题:

在这里插入图片描述

insert会返回一个迭代器,此迭代器的
返回的是新插入元素的迭代器

在这里插入图片描述

请看下图理解:

在这里插入图片描述
所以以后我们可以这样写代码:

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);
v.push_back(7);
vector<int>::iterator it = v.begin();
while(it!=v.end())
{
	it = insert(it,100);
	it+=2;
}
for (auto e : v)
{
	cout << e << " ";
}
cout << endl;

在每一个元素前插入一个100

  1. 对于erase来说

删除后不用再++迭代器
只用在没删除的时候再++

vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
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;
	}
}
for (auto e : v)
{
	cout << e << " ";
}
cout << endl;


5. 对于reverse的深度剖析

众所周知,reverse只改变capacity大小
而不会改变size的大小

所以这样写代码是有问题的:

vector<int> vv;
vv.reverse(10);//开辟10份空间
for(int i=0;i<10;i++)
{
	vv[i]=i;
}

因为size此时是0,也就是有效长度为0
虽然你开辟了10份空间,但是运算符
操作[ ]的内部实现会检查下标:

T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}

所以使用reverse后直接用[ ]
访问会报错,这也是很多人会出错的地方!


6. vector深浅拷贝问题

首先来看看以下代码:

vector<vector<int>> vv(3,vector<int>(5));

这是一个二维数组,初始化为三行五列

vector<vector<int>> vv(3,vector<int>(5));
vector<vector<int>> x(vv);

这是在拷贝构造类对象x

自我实现的拷贝构造使用的是memcpy:

Vector(const Vector<T>& v)
{
	assert(v._start && v._finish && v._endofsto);
	_start = new T[v.capacity()];//给size或capacity都可以
	memcpy(_start, v._start, sizeof(T) * v.size());
}

然而memcpy是逐个字节拷贝
当数组是一维时,用memcpy没有问题
但是当数组是二维数组时,会出错!

我们在VS上调试窗口的监视查看地址信息:

在这里插入图片描述

会发现,虽然x的地址和vv的地址不同
但是vv中的迭代器和x中的迭代器
的地址是相同的也就是指向同一份空间

可以用下图来理解这个过程:

在这里插入图片描述


7. vector深浅拷贝的解决方法

由于这种深浅拷贝问题是因为memcpy
导致的,所以这里不能使用memcpy
只需要老实的使用一个for循环就能解决:

修改后的代码:

Vector(const Vector<T>& v)
{
	assert(v._start && v._finish && v._endofsto);
	_start = new T[v.capacity()];//给size或capacity都可以
	//memcpy(_start, v._start, sizeof(T) * v.size()); //使用memcpy时,数组是二维数组会发生问题
	for (size_t i = 0; i < size(); i++)
	{
		_start[i] = v._start[i];
		_finish = _start + v.size();
	}
	_endofsto = _start + v.capacity();
}

直接使用等号=是外部和内部都是
原来的一份拷贝,这样就能解决问题了


8. 总结以及拓展

vector的自我实现的目的不是
为了实现一个比库中更好的vector
而是为了带大家熟悉vector的使用
并且了解了内部实现后,以后用vector
时出现问题可以很快的排查出来!

拓展:vector自我实现全部代码链接:

gitee代码仓库


🔎 下期预告:链表接口熟悉以及模拟实现 🔍

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

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

相关文章

Flink CDC学习笔记

第一章 CDC简介 1.1 什么是CDC ​ CDC (Change Data Capture 变更数据获取&#xff09;的简称。核心思想就是&#xff0c;检测并获取数据库的变动&#xff08;增删查改&#xff09;&#xff0c;将这些变更按发生的顺序记录下来&#xff0c;写入到消息中间件以供其它服务进行订…

前端基础---HTML笔记汇总一

HTML定义 HTML超文本标记语言——HyperText Markup Language。 超文本是什么&#xff1f; 链接标记是什么&#xff1f; 标记也叫标签&#xff0c;带尖括号的文本 标签分类 单标签:只有开始标签&#xff0c;没有结束标签(<br>换行 <hr>水平线 <img> 图像标…

Facebook message tag 使用攻略

Messenger 讯息传不出去&#xff1f;无法发送FB 讯息给非好友&#xff1f; 2020年3月&#xff0c;Facebook 为了防止用户被过多的推广或垃圾讯息困扰而更新使用条款&#xff0c;现在商家要用FB传讯息给所有人&#xff08;包括非好友&#xff09;&#xff0c;应该使用 Facebook …

C语言每日一练------Day(5)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;错误的集合 密码检查 &#x1f493;博主csdn个人主页&#xff1a;小小u…

HTTP:http上传文件的原理及java处理方法的介绍

为了说明原理&#xff0c;以下提供一个可以上传多个文件的例子&#xff0c;html页面代码如下&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>http upload file</title> </head> <body>…

字节一面:你能讲一下跨域吗

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;作为一名前端开发工程师&#xff0c;我们日常开发中与后端联调时一定会遇到跨域的问题&#xff0c;只有处理好了跨域才能够与后端交互完成需求&#xff0c;所以深入学习跨域…

SpringMVC-学习笔记

文章目录 1.概述1.1 SpringMVC快速入门 2. 请求2.1 加载控制2.2 请求的映射路径2.3 get和post请求发送2.4 五种请求参数种类2.5 传递JSON数据2.6 日期类型参数传递 3.响应3.1 响应格式 4.REST风格4.1 介绍4.2 RESTful快速入门4.3 简化操作 1.概述 SpringMVC是一个基于Java的Web…

我与GPT的一次关于Orb-SLAM3源码(包括2)的深入对话

目录 一、前言二、关于Orb-SLAM3的代码结构三、关于system3.1 关于摄像头初始化3.2 关于摄像头模型化3.2关于初始化 四、关于ORBVocabulary五、关于优化六、小结 一、前言 Orb-SLAM2或者3是一个开源的视觉SLAM框架&#xff0c;里面的一些思想&#xff0c;一些软件工程的设计理…

Java设计模式-状态模式

1.概述 定义&#xff1a; 对有状态的对象&#xff0c;把复杂的“判断逻辑”提取到不同的状态对象中&#xff0c;允许状态对象在其内部状态发生改变时改变其行为。 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状…

验证go循环删除slice,map的操作和map delete操作不会释放底层内存的问题

目录 切片 for 循环删除切片元素其他循环中删除slice元素的方法方法1方法2&#xff08;推荐&#xff09;方法3 官方提供的方法结论 切片 for 循环删除map元素goalng map delete操作不会释放底层内存go map原理源码CRUD查询新增 操作注意事项map元素是无法取址的map是线程不安全…

亚马逊鲲鹏系统是怎么操作测评的

亚马逊鲲鹏系统可以注册亚马逊买家号、养号、下单留评等&#xff0c;是一款功能比较齐全的测评软件&#xff0c;具体操作如下&#xff1a; 首先我们需要先准备好买家账号&#xff0c;账号可以直接去购买已经注册好了的账号&#xff0c;也可以准备好账号所需要的一些邮箱、ip、…

RealVNC配置自定义分辨率(AlmaLinux 8)

RealVNC 配置自定义分辨率&#xff08;AlmaLinux8&#xff09; 参考RealVNC官网 how to set up resolution https://help.realvnc.com/hc/en-us/articles/360016058212-How-do-I-adjust-the-screen-resolution-of-a-virtual-desktop-under-Linux-#standard-dummy-driver-0-2 …

docker部署nginx,部署springboot项目,并实现访问

一、先部署springboot项目 1、安装docker&#xff1a; yum install docker -y 2、启动docker&#xff1a; service docker start 重启&#xff1a; service docker restart 3、查看版本&#xff1a; docker -v 4、使设置docker.service生效&#xff08;路径&#xff1a;…

国产自主可控C++工业软件可视化图形架构源码

关于国产自主代替的问题是当前热点&#xff0c;尤其是工业软件领域。 “一个功能强大的全自主C跨平台图形可视化架构对开发自主可控工业基础软件至关重要&#xff01;” 作为全球领先的C工业基础图形可视化软件提供商&#xff0c;UCanCode软件有自己的思考&#xff0c;我们认…

uniapp 开发小程序,封装一个方法,让图片使用线上地址

1.在main.js文件中&#xff0c;添加以下代码&#xff1a; 复制使用&#xff1a; // 图片使用网络地址 Vue.prototype.localImgSrc function(img){//项目的地址域名&#xff0c;例如百度return "https://baidu.cn/static/index/images/" img; }2.在页面中直接使用&…

SSM - Springboot - MyBatis-Plus 全栈体系(二)

第一章 Maven 三、Maven 核心功能依赖和构建管理 1. 依赖管理和配置 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程…

MongoDB实验——在MongoDB集合中查找文档

在MongoDB集合中查找文档 一、实验目的二、实验原理三、实验步骤1.启动MongoDB数据库、启动MongoDB Shell客户端2.数据准备-->person.json3.指定返回的键4 .包含或不包含 i n 或 in 或 in或nin、$elemMatch&#xff08;匹配数组&#xff09;5.OR 查询 $or6.Null、$exists7.…

MySQL事物和存储引擎

事务 一、MySQL事务的概念 事务是一种机制、一个操作序列&#xff0c;包含了一组数据库操作命令&#xff0c;并且把所有的命令作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这一组数据库命令要么都执行&#xff0c;要么都不执行。 事务是一个不可分割的工作逻辑单…

基于ssm+vue汽车售票网站源码和论文

基于ssmvue汽车售票网站源码和论文088 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让…

基于RabbitMQ的模拟消息队列之三——硬盘数据管理

文章目录 一、数据库管理1.设计数据库2.添加sqlite依赖3.配置application.properties文件4.创建接口MetaMapper5.创建MetaMapper.xml文件6.数据库操作7.封装数据库操作 二、文件管理1.消息持久化2.消息文件格式3.序列化/反序列化4.创建文件管理类MessageFileManager5.垃圾回收 …
最新文章