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

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

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

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

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


在这里插入图片描述

vector-下

  • 1. 前言
  • 2. 什么是迭代器失效?
  • 3. 迭代器失效的经典案例
  • 4. 迭代器失效的解决方案
  • 5. 对于reserve的深度剖析
  • 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删除后,后面的数据会覆盖过来
此时不让迭代器++它也指向下一个位置

注:在VS编译器中.只要使用了erase函数
编译器自动认为此位置迭代器失效
所以在VS上进行多次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. 对于reserve的深度剖析

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

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

vector<int> vv;
vv.reserve(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];
}

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


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.mzph.cn/news/63613.shtml

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

相关文章

GitHub 标星 15w,如何用 Python 实现所有算法?

学会了 Python 基础知识&#xff0c;想进阶一下&#xff0c;那就来点算法吧&#xff01;毕竟编程语言只是工具&#xff0c;结构算法才是灵魂。 新手如何入门 Python 算法&#xff1f; 几位印度小哥在 GitHub 上建了一个各种 Python 算法的新手入门大全。从原理到代码&#xf…

ClickHouse进阶(六):副本与分片-2-Distributed引擎

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

【项目设计】高并发内存池(Concurrent Memory Pool)

目录 1️⃣项目介绍 &#x1f359;项目概述 &#x1f359;知识储备 2️⃣内存池介绍 &#x1f359;池化技术 &#x1f359;内存池 &#x1f359;内存池主要解决的问题 &#x1f365;内碎片 &#x1f365;外碎片 &#x1f359;malloc 3️⃣ 定长内存池设计 4️⃣ 项…

channel并发编程

不要通过共享内存通信&#xff0c;要通过通信共享内存。 channel是golang并发编程中一种重要的数据结构&#xff0c;用于多个goroutine之间进行通信。 我们通常可以把channel想象成一个传送带&#xff0c;将goroutine想象成传送带周边的人&#xff0c;一个传送带的上游放上物品…

打破对ChatGPT的依赖以及如何应对ChatGPT的错误和幻觉

​ OpenAI的ChatGPT是第一个真正流行的生成式AI工具&#xff0c;但它可能不是最好的。现在是时候扩大你的AI视野了。 ChatGPT成为了基于大语言模型(LLM)的聊天机器人的同义词。但是现在是时候停止对ChatGPT的痴迷&#xff0c;开始发现这个新世界中强大的替代品了。 首先&a…

【内推码:NTAMW6c】 MAXIEYE智驾科技2024校招启动啦

MAXIEYE智驾科技2024校招启动啦【内推码&#xff1a;NTAMW6c】 【招聘岗位超多&#xff01;&#xff01;公司食堂好吃&#xff01;&#xff01;】 算法类&#xff1a;感知算法工程师、SLAM算法工程师、规划控制算法工程师、目标及控制算法工程师、后处理算法工程师 软件类&a…

python 深度学习 解决遇到的报错问题4

目录 一、DLL load failed while importing _imaging: 找不到指定的模块 二、Cartopy安装失败 三、simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 四、raise IndexError("single positional indexer is out-of-bounds") 五、T…

操作系统中一些零散的知识点

第三章 内存管理 在虚拟内存系统中&#xff0c;虚拟内存的最大容量是由计算机的地址结构&#xff08;CPU寻址范围&#xff09;确定的&#xff0c;而虚拟内存的实际容量是受到“内存大小磁盘空间大小”、“地址线位数”共同制约&#xff0c;取二者最小值实现虚拟内存管理必须有…

Lvs+KeepAlived高可用高性能负载均衡

目录 1.环境介绍 2.配置keepalived 3.测试 1.测试负载均衡 2.测试RS高可用 3.测试LVS高可用 3.1测试lvs主服务宕机 3.2.测试lvs主服务器恢复 4.我在实验中遇到的错误 1.环境介绍 环境&#xff1a;centos7 RS1---RIP1:192.168.163.145 VIP 192.168.163.200 RS2---RIP2…

无涯教程-Android Studio函数

第1步-系统要求 您将很高兴知道您可以在以下两种操作系统之一上开始Android应用程序的开发- MicrosoftWindows10/8/7/Vista/2003(32或64位)MacOSX10.8.5或更高版本,最高10.9(小牛) GNOME或KDE桌面 第二点是,开发Android应用程序所需的所有工具都是开源的,可以从Web上下载。以…

【Interaction交互模块】ActionPublisher/ActionReciever

文章目录 需求案例原理0、相应准备1、发布器、接收器2、将把两者联系起来3、前提状态 补充 需求 Interactor只能将一个动作&#xff08;如按下手柄抓取键&#xff09;&#xff0c;传递给Interactble,如果要传两个或多个&#xff0c;就需要用发布器和接收器。 案例 右手柄抓取…

小疆智控CANOpen转PROFINET网关连接EA180C CANOPEN总线型伺服配置案例

1、首先新建一个工程&#xff0c;在CanOpen转Profinet网关配置软件中添加主站设备&#xff0c;如下图&#xff1b; 2、在CanOpen转Profinet网关设备点击导入EA180C CANOPEN总线型伺服 EDS 文件&#xff0c;右键添加从属设备&#xff1b; 3、设备设置站地址&#xff0c;如图&…

云原生架构:在云环境中构建弹性应用

随着云计算技术的快速发展&#xff0c;云原生架构已经成为现代软件开发的热门话题。作为一种在云环境中构建和运行应用程序的方法论&#xff0c;云原生架构强调弹性、可扩展性和灵活性&#xff0c;使开发者能够更好地应对复杂的业务需求。本文将深入探讨云原生架构的核心概念、…

TDesign表单rules通过函数 实现复杂逻辑验证输入内容

Element ui 中 我们可以通过validator 绑定函数来验证一些不在表单model中的值 又或者处理一下比较复杂的判断逻辑 TDesign也有validator 但比较直观的说 没有Element那么好用 这里 我们给validator绑定了我们自己的checkAge函数 这个函数中 只有一个参数 value 而且 如果你的…

一种改进多旋翼无人机动态仿真的模块化仿真环境研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ChatGPT 总结前端HTML, JS, Echarts都包含哪些内容

AIGC ChatGPT ,BI商业智能, 可视化Tableau, PowerBI, FineReport, 数据库Mysql Oracle, Office, Python ,ETL Excel 2021 实操,函数,图表,大屏可视化 案例实战 http://t.csdn.cn/zBytu

Web3.0:重新定义互联网的未来

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Web3.0&#xff1a;重新定义互联网的未来 Web3.0是指下一代互联网&#xff0c;也称为“分布式互联网”。相比于Web1.0和Web2.0&#xff0c;Web3.0具有更强的去中心化、…

京东API接口解析,实现获得JD商品评论

要获取京东商品评论&#xff0c;需要使用京东的开放平台API接口。以下是一个基本的示例&#xff0c;解析并实现获取JD商品评论的API接口。 首先&#xff0c;你需要访问京东开放平台并注册一个开发者账号。注册完成后&#xff0c;你需要创建一个应用并获取到API的权限。 在获取…

SQL Server开启变更数据捕获(CDC)

一、CDC简介 变更数据捕获&#xff08;Change Data Capture &#xff0c;简称 CDC&#xff09;&#xff1a;记录 SQL Server 表的插入、更新和删除操作。开启cdc的源表在插入、更新和删除操作时会插入数据到日志表中。cdc通过捕获进程将变更数据捕获到变更表中&#xff0c;通过…

【OpenCV入门】第六部分——腐蚀与膨胀

文章结构 腐蚀膨胀开运算闭运算形态学方法梯度运算顶帽运算黑帽运算 腐蚀 腐蚀操作可以让图像沿着自己的边界向内收缩。OpenCV通过”核“来实现收缩计算。“核”在形态学中可以理解为”由n个像素组成的像素块“&#xff0c;像素块包含一个核心&#xff08;通常在中央位置&…