【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.rhkb.cn/news/116203.html

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

相关文章

使用环境中的视觉地标和扩展卡尔曼滤波器定位移动机器人研究(Matlab代码实现)

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

Solidity 小白教程:6. 引用类型, array, struct

Solidity 小白教程&#xff1a;6. 引用类型, array, struct 这一讲&#xff0c;我们将介绍solidity中的两个重要变量类型&#xff1a;数组&#xff08;array&#xff09;和结构体&#xff08;struct&#xff09;。 数组 array 数组&#xff08;Array&#xff09;是solidity常…

Java中支持分库分表的框架/组件/中间件简介

文章目录 1 sharding-jdbc2 TSharding3 Atlas4 Cobar5 MyCAT6 TDDL7 Vitess 列举一些比较常见的&#xff0c;简单介绍一下&#xff1a; sharding-jdbc&#xff08;当当&#xff09; TSharding&#xff08;蘑菇街&#xff09; Atlas&#xff08;奇虎360&#xff09; Cobar&#…

基于OpenCV+LPR模型端对端智能车牌识别——深度学习和目标检测算法应用(含Python+Andriod全部工程源码)+CCPD数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境OpenCV环境Android环境1. 开发软件和开发包2. JDK设置3. NDK设置 模块实现1. 数据预处理2. 模型训练1&#xff09;训练级联分类器2&#xff09;训练无分割车牌字符识别模型 3. APP构建1&#xff09;导入OpenCV库…

deepspeed多机多卡并行训练指南

文章目录 前言离线配置训练环境共享文件系统多台服务器之间配置互相免密登录pdsh多卡训练可能会碰到的问题注意总结 前言 我的配置&#xff1a; 7机14卡&#xff0c;每台服务器两张A800 问&#xff1a;为啥每台机只挂两张卡&#xff1f; 答&#xff1a;给我的就这样的&#…

Midjourney学习(三)6个高级应用

使用Remix Mode在原图片的基础上进行二次创作 通过prompt得到大图之后&#xff0c;点击Make Variations按钮&#xff0c;输入Remix Prompt&#xff0c;即可得到意想不到的效果&#xff01; 局部内容重绘 通过局部重绘可以实现对画面内容更加精细化的控制&#xff0c;同样也是需…

[C/C++]指针详讲-让你不在害怕指针

个人主页&#xff1a;北海 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C/C&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;大家一起学习交流&#xff01;&#x1f9…

无涯教程-JavaScript - NEGBINOMDIST函数

NEGBINOMDIST函数取代了Excel 2010中的NEGBINOM.DIST函数。 描述 该函数返回负二项式分布。 NEGBINOMDIST返回在第number_s次成功之前出现number_f次失败的概率,而成功的恒定概率是概率_s。 该函数与二项式分布相似,不同之处在于成功次数是固定的,而试验次数是可变的。像二项…

基于51单片机的SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 讲解视频1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&下载链接 51单片机SHT11温湿度上下限LCD12864显示报警仿真设计( proteus仿…

JVM 垃圾收集

垃圾收集 分代理论Java 堆的内存分区不同分代收集垃圾收集算法 分代理论 弱分代假说&#xff1a;绝大多数对象都是朝生夕灭&#xff0c;即绝大多数对象都是用完很快需要销毁的。强分代假说&#xff1a;熬过多次垃圾收集过程的对象就越难以消亡&#xff0c;即如果对象经过多次垃…

浏览器连不上 Flink WebUI 8081 端口

安装 flink-1.17.0 后&#xff0c;start-cluster.sh 启动&#xff0c;发现浏览器连不上 Flink WebUI 的8081端口。 问题排查&#xff1a; command R&#xff0c;输入cmd&#xff0c;检查宿主机能否ping通虚拟机&#xff0c;发现能ping通。 检查是否有flink以外的任务占用8081…

Linux网络编程 网络基础知识

目录 1.网络的历史和协议的分成 2.网络互联促成了TCP/IP协议的产生 3.网络的体系结构 4.TCP/IP协议族体系 5.网络各层的协议解释 6.网络的封包和拆包 7.网络预备知识 1.网络的历史和协议的分成 Internet-"冷战"的产物 1957年十月和十一月&#xff0c;前苏…

MusicBrainz Picard for Mac :音乐文件ID3编辑器

MusicBrainz Picard for Mac是一款macOS平台的音乐文件ID3编辑器&#xff0c;能够帮助我们在Mac电脑上编辑音乐文件的ID3标签信息&#xff0c;包括艺人、专辑等信息&#xff0c;非常快速和简单方便。Picard是下一代MusicBrainz标记应用程序。 这个新的标签概念是面向专辑的&…

美客多(mercadolibre)测评下单技术(养号环境搭建详解)

MercadoLibre&#xff08;美客多&#xff09;是拉丁美洲的一个网购平台。该公司为其客户提供电子商务交易的购买&#xff0c;出售&#xff0c;支付和收集机制。目前全球第十大电商市场——巴西是MercadoLibre的主要市场&#xff0c;占据近60%的平台营收&#xff0c;接着是阿根廷…

4.(Python数模)0-1规划

Python解决0-1规划问题 参考下面文章 源代码 import pulp # 导入 pulp 库# 主程序 def main():# 投资决策问题&#xff1a;# 公司现有 5个拟投资项目&#xff0c;根据投资额、投资收益和限制条件&#xff0c;问如何决策使收益最大。"""问题建模&#x…

PyTorch 模型性能分析和优化 - 第 3 部分

这[1]是关于使用 PyTorch Profiler 和 TensorBoard 分析和优化 PyTorch 模型主题的系列文章的第三部分。我们的目的是强调基于 GPU 的训练工作负载的性能分析和优化的好处及其对训练速度和成本的潜在影响。特别是&#xff0c;我们希望向所有机器学习开发人员展示 PyTorch Profi…

PHP8的数组-PHP8知识详解

今天开始学习数组&#xff0c; 本文主要讲了三点&#xff1a;什么是数组、php8中数组的改进、数组函数。 一、什么是数组 在PHP8中&#xff0c;数组是非常重要的数据类型。相对于其他的数据类型&#xff0c;数组更像一种结构&#xff0c;而这种结构可以储存一系列数值。 数组…

设计模式之桥接模式

文章目录 手机操作问题传统方案解决手机操作问题传统方案解决手机操作问题分析桥接模式(Bridge)-基本介绍桥接模式(Bridge)-原理类图桥接模式解决手机操作问题桥接模式的注意事项和细节桥接模式其它应用场景常见的应用场景: 手机操作问题 现在对不同手机类型的不同品牌实现操作…

国标视频融合云平台EasyCVR视频汇聚平台关于远程控制的详细介绍

EasyCVR国标视频融合云平台是一个能在复杂网络环境下统一汇聚、整合和集中管理各类分散视频资源的平台。该平台提供了多种视频能力和服务&#xff0c;包括视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、集群、电子地图、H.265视频自动转码和智能分析等…

安圭拉变成AI领域的数字金矿?

这个小小的岛国今年的域名销售额可能达到其GDP的10%&#xff01; 安圭拉a小小的英国岛屿领土在加勒比海地区&#xff0c;由于其“可再生能源”&#xff0c;今年可能带来高达3000万美元的收入。ai”域名&#xff0c;报告彭博在周四发表的一篇文章中说。在过去的一年里&#xff0…