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

news2024/12/25 14:28:07

💓博主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.coloradmin.cn/o/947833.html

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

相关文章

【Linux】Linux线程的同步与互斥

前言 如果你对进程/线程中通信的相关概念不太了解的话可以先看这里《进程间通信的基础概念》 Linux线程的同步与互斥 一、Linux线程的互斥1、互斥的相关背景2、互斥量的接口3、互斥量实现原理探究 二、可重入与线程安全1、概念2、常见的线程不安全的情况3、常见的线程安全的情况…

【前端】 Layui点击图片实现放大、关闭效果

实现效果&#xff1a;点击图片实现放大&#xff0c;点击空白处关闭效果。下图。 实现逻辑&#xff1a;二维码是使用JQ插件生成的&#xff0c;点击二维码&#xff0c;获取图片路径&#xff0c;通过Layui的弹窗显示放大后的图片。 Html <div id"qrcode" class&quo…

【JAVA基础】数据类型,逻辑控制

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 数据类型整型变量 int长整型变量 long单精度浮点数 float双精度浮点数 double字符类型 char字节…

Mybatis执行getById报错Parameter index out of range

博主使用的Springboot3.0&#xff0c;jdk17&#xff0c;MybatisMybatisFlex的环境 报错&#xff1a; org.mybatis.spring.MyBatisSystemException: null java.lang.RuntimeException: java.sql.SQLException: Parameter index out of range (1 > number of parameters, w…

Android DataBinding 基础入门(学习记录)

目录 一、DataBinding简介二、findViewById 和 DataBinding 原理及优缺点1. findViewById的优缺点2. DataBinding的优缺点 三、Android mvvm 之 databinding 原理1. 简介和三个主要的实体DataViewViewDataBinding 2.三个功能2.1. rebind 行为2.2 observe data 行为2.3 observe …

基于MQTT协议的物联网网关实现远程数据采集及监控

在数字化时代的浪潮中&#xff0c;工业界正面临着前所未有的变革与机遇。而在这场变革中&#xff0c;基于MQTT协议的物联网网关崭露头角&#xff0c;成为连接工业设备、实现远程数据采集与监控的利器。其中&#xff0c;HiWoo Box作为一款出色的工业边缘网关&#xff0c;引领着这…

搜索算法之内容质量评估:如何对作者和文章进行质量评价

paperClubIP属地: 江苏 编辑文章 对于搜索引擎而言&#xff0c;用户算法的核心价值是用户体验&#xff0c;包括搜索内容的相关性、内容质量及时效性等&#xff0c;其中内容质量是前置步骤&#xff0c;既可以用于优质内容源筛选&#xff0c;又可以作为搜索召回结果排序因素&am…

Leetcode.100 相同的树

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 代码如下&#xff1a;…

【STM32】串口初步使用

本文只作为学习笔记&#xff0c;对串口进行一个简单的介绍&#xff0c;正确的使用方式还需要进行实际的调试 通信的类型&#xff1a; 同步 异步 单工 双工 串行 并行 STM32的串口通信&#xff1a; 配置片上外设的控制寄存器&#xff0c;通信双方进行相同的配置&#xff0c;…

【Vue】集成高德地图

Vue 集成高德地图 步骤 注册成为高德开发者 获取申请的安全密钥、申请好的Web端开发者Key 创建Vue 工程 创建地图组件 引入地图组件 高德地图开发平台地址 https://lbs.amap.com/官方示例地址 https://lbs.amap.com/demo/list/js-api-v2示例 首先创建一个vue工程 安装 npm …

jvm与锁

今天是《面霸的自我修养》的第二弹&#xff0c;内容是Java并发编程中关于Java内存模型&#xff08;Java Memory Model&#xff09;和锁的基础理论相关的问题。这两块内容的八股文倒是不多&#xff0c;但是难度较大&#xff0c;接下来我们就一起一探究竟吧。 数据来源&#xff…

CAC2.0准实时威胁检测,“无人化”防暴力破解

客户背景 上海微盟企业发展有限公司&#xff08;以下简称“微盟”&#xff09;&#xff0c;香港主板上市企业&#xff0c;成立于2013年&#xff0c;致力于为商家打造去中心化的数字化转型SaaS产品及全链路增长服务&#xff0c;助力商家经营可持续增长。 在这个快节奏的网络时…

《发电厂电气部分》进出线停送电倒闸操作理解

在《发电厂电气部分》&#xff08;部分学校也叫《供配电技术》&#xff09;中&#xff0c;停电/送电时的倒闸操作在笔者看来是比较难理解的一件事&#xff0c;即使是在bilibili上看了实际架空线路上的倒闸操作&#xff0c;还是感觉云里雾里。这里分享一下自己的理解。 这里以最…

对于前端模块化的理解与总结(很全乎)

目录 模块化的好处 模块化的commonJS导入导出 暴露(导出)模块&#xff1a;module.exports value或exports.xxx value 导入模块——使用 es6模块化 方法一逐个导出 方法二默认导出 方法三 方法四 方法五 export 和import 同时存在 多个文件导出到一个文件后在相关文件…

解决 quill Cannot import modules/imageResize. Are you sure it was registered?

这个插件是在富文本编辑器中调整图片大小的,发现拉下来的依赖会报错,于是替换了nodejs版本,没有解决,但是用同时之前拉下来的,莫名其妙正常,后来经过尝试,发现删除demo和node_modules文件夹后正常. 删除后,不报错,正常了

python遍历文件夹下的所有子文件夹,并将指定的文件复制到指定目录

python遍历文件夹下的所有子文件夹&#xff0c;并将指定的文件复制到指定目录 需求复制单个文件夹遍历所有子文件夹中的文件&#xff0c;并复制代码封装 需求 在1文件夹中有1&#xff0c;2两个文件夹 将这两个文件夹中的文件复制到 after_copy中 复制单个文件夹 # coding: ut…

电源管理(PMIC)TPS63070RNMR、TPS650942A0RSKR、LM5175RHFR器件介绍、应用及特点。

一、TPS63070RNMR&#xff0c;降压升压 开关稳压器 IC 正 可调式 2.5V 1 输出 3.6A&#xff08;开关&#xff09; 15-PowerVFQFN 1、概述 TPS63070高输入电压降压-升压转换器是一款高效的低静态电流降压-升压转换器。这些器件适用于输入电压高于或低于输出电压的应用。升压模式…

Java-day12(泛型)

泛型 解决元素存储的安全性问题 解决获取数据元素时&#xff0c;需要类型强转的问题 核心思想&#xff1a;把一个集合中的内容限制为一个特定的数据类型 静态方法中不能使用类的泛型 不能在catch中使用泛型 如果泛型类是一个接口或抽象类&#xff0c;则不可创建泛型类的对…

Docker技术--Docker镜像管理

1.Docker镜像特性 ①.镜像创建容器的特点 Docker在创建容器的时候需要指定镜像,每一个镜像都有唯一的标识:image_id,也可也使用镜像名称和版本号做唯一的标识,如果不指定版本号,那么默认使用的是最新的版本标签(laster)。 ②.镜像分层机制 Docker镜像是分层构建的,并通过…

Springboot集成Docker并将镜像推送linux服务器

案例使用springboot项目&#xff0c;在IDEA 中集成Docker生成镜像&#xff0c;并将镜像发布到linux服务器 具体步骤如下&#xff1a; 1、Centos7安装Docker 更新系统的软件包列表 sudo yum update安装Docker所需的软件包和依赖项&#xff1a; sudo yum install docker完成…