39 vector深入理解 · 迭代器失效深度浅拷贝

目录

一、迭代器失效

(一)外部迭代器失效

1、扩容引起的野指针问题

2、删除引起的逻辑问题

二、深度浅拷贝


一、迭代器失效

        迭代器可以理解为像指针一样的类对象,但不要一味地认为迭代器就是指针,指针可以实现迭代器,但迭代器也可能是自定义类型,可以通过以下函数调用查看具体类型:

typeid(类型或者对象).name()typeid(vector<int>::iterator).name()

(一)外部迭代器失效

        上一节的末尾介绍的是一种内部迭代器失效,也有外部迭代器失效:

1、扩容引起的野指针问题

        如下代码演示:

void test_vector01()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e << " ";}// 打印 1 2 3 4cout << endl;int x;cin >> x;auto it = find(v1.begin(),v1.end(),x);if (it != v1.end()){v1.insert(it, 10 * x);cout << *it << endl;}                
}

        以上代码的功能是在x位置前插入10*x这个数。但在最后的 cout << *it << endl 处会发生迭代器失效的问题:因为 insert函数 是用形参接收实参,形参的改变不影响实参,insert函数虽然修正了形参pos的位置,但实参 it 的位置没有改变,依旧指向被释放的旧空间,是野指针,解引用野指针就会出现报错。

        将insert函数的迭代器参数修改为iterator& pos是不是就可以了?

        答:不行。因为若【迭代器运算的结果】做实参,如:it+2 传过去,这个it+2的结果会保留在一个临时的变量中,而临时变量具有常性,即不可修改(被const修饰),iterator&的话就会造成权限放大的问题。所以不能这样访问,且迭代器只有扩容的情况下才会失效,但我们又不知道什么时候扩容,所以默认迭代器失效了就不能访问

        解决:insert的返回值为【更新后的pos位置】,需要 insert 的话要用 it 接收新的 it 位置。insert代码修改如下:

iterator insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage)//扩容{size_t len = pos - _start;//扩容前计算相对位置reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//扩容后得出新的pos的位置,这样就不会出现迭代器失效的问题}iterator i = _finish - 1;while (i >= pos){*(i + 1) = *i;i--;}*pos = x;_finish++;return pos;
}

        将演示代码中插入部分的代码修改为:

if (it != v1.end()){it = v1.insert(it, 10 * x);cout << *it << endl;}   

        即可正常访问 it 迭代器的数据。

        总结:外部迭代器第一种失效的原因为扩容引起的野指针的问题

2、删除引起的逻辑问题

        演示代码一:简单删除指定位置数据

void test_vector01()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e << " ";}cout << endl;int x;cin >> x;auto it = find(v1.begin(),v1.end(),x);if (it != v1.end()){v1.erase(it);cout << *it << endl;}                
}

        运行后vs进行强制检查后报错,迭代器失效后不让访问 *it。这就是删除数据后,导致数据挪动,it 已经不是指向原来的位置了,会导致逻辑问题。

        下面演示代码更能体现:

void test_vector01()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e << " ";}cout << endl;//删除所有的偶数auto it = v1.begin();while(it != v1.end()){if(*it % 2 == 0)v1.erase(it);  ++it;                                                                        }for(auto& e : v1){cout << e << " ";     }                    
}

        因为代码在vs中运行会报错,以下结果在g++中运行得出:

        若数据为:1 2 3 4 5 ,删除后结果为1 3 5;

        但数据为:1 2 2 3 4 5,删除后的结果为1 2 3 5,代码的逻辑就会出现问题;

        若数据为:1 2 2 3 4 5 2,删除后出现段错误(程序崩溃)。

        通过以上数据可以看出是在进行删除后迭代器进行移动而造成的逻辑错误,那么修为进行删除后不挪动迭代器,而不需要删除则进行++能否解决问题?代码如下:

while(it != v1.end())
{if(*it % 2 == 0)v1.erase(it);  else++it;                                                                                 
}

        改进后的代码不能解决vs平台的问题,依旧报错,若有删除到一定有效元素个数后就进行缩容的功能,即异地缩容出现,也会失效。

        所以要访问 it迭代器 指向的对象则必须更新 it 的指向,更新的指向为erase函数返回的删除位置。代码如下:

while(it != v1.end())
{if(*it % 2 == 0)it = v1.erase(it);  else++it;                                                                                       
}

        总结:失效的迭代器任何场景下都不能访问位置,更新后的迭代器才能访问。

        注意:erase函数若不更新返回值,指向当前元素的pos迭代器失效,后面元素会牵扯到移动数据,因此pos指向的需要删除的元素后面的迭代器也全部失效。

二、深度浅拷贝

        区分深拷贝与浅拷贝的概念:在自定义类型中,默认的拷贝构造会完成值拷贝(浅拷贝 ):地址与值完全相等地进行拷贝,那么析构原对象与拷贝对象的话就会对同一块空间析构两次,且修改原对象会影响拷贝对象的内容;此时就需要深拷贝,即原对象与拷贝对象的地址不同但值相等。

        当push_back的参数为string类型且扩容的时候会出问题,问题的核心为深层次的深浅拷贝问题,push_back所涉及的代码如下:

void push_back(const T& x)
{insert(_finish, x);
}iterator insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage)//扩容{size_t len = pos - _start;//扩容前计算相对位置reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//扩容后得出新的pos的位置,这样就不会出现迭代器失效的问题}iterator i = _finish - 1;while (i >= pos)//向后挪数据,腾出空间进行插入{*(i + 1) = *i;i--;}*pos = x;_finish++;return pos;
}void reserve(size_t n)//扩容
{if (n > capacity()){size_t OldSize = size();T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * OldSize);delete[] _start;}_start = tmp;_finish = _start + OldSize;_endofstorage = _start + n;}
}

        解析:

        进行扩容的三部曲:

                ① 开辟一个目标容量大小的空间;

                ② 把旧空间的值拷贝给新空间;

                ③ 更新对象的属性。

        在进行 ① 的内存图如下:

        在 ② 中进行memcpy的时候,进行的是浅拷贝,会把string对象数组中string对象的指针都原封不动地拷贝过来,与旧空间的sting指针指向的同一堆中地址的空间,当旧空间被释放的时候,新空间的string指针依旧指向旧的被释放的空间,就是野指针问题,就会出现乱码。内存图如下:

        改进:把memcpy改成for循环拷贝进行赋值,这样的话若是内置类型就会直接拷贝,若是自定义类型就会调用它本身的深拷贝,不需要过多考虑。改进后代码如下:

void reserve(size_t n)//扩容
{if (n > capacity()){size_t OldSize = size();//扩容三部曲://① new出新空间T* tmp = new T[n];if (_start){//② 拷贝原空间的数据到新空间//memcpy(tmp, _start, sizeof(T) * OldSize);reserve string对象时会出错for (size_t i = 0; i < OldSize; i++){tmp[i] = _start[i];}//③ 释放旧空间并更改原空间的成员变量的值delete[] _start;}_start = tmp;_finish = _start + OldSize; _endofstorage = _start + n;}
}

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

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

相关文章

2024年认证杯SPSSPRO杯数学建模C题(第一阶段)云中的海盐解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 C题 云中的海盐 原题再现&#xff1a; 巴黎气候协定提出的目标是&#xff1a;在2100年前&#xff0c;把全球平均气温相对于工业革命以前的气温升幅控制在不超过2摄氏度的水平&#xff0c;并为1.5摄氏度而努力。但事实上&#xff0c;许多之前的…

AI智能体Prompt预设词指令大全+GPTs应用使用

AI智能体使用指南 直接复制在AI工具助手中使用&#xff08;提问前&#xff09; 可前往SparkAi系统用户官网进行直接使用 SparkAI系统介绍文档&#xff1a;Docs 常见AI智能体GPTs应用大全在线使用 自定义添加制作AI智能体进行使用&#xff1a; 文章润色器 你是一位具有敏锐洞察…

Origin快速拟合荧光寿命、PL Decay (TRPL)数据分析处理-方法二

1.先导入数据到origin 2.导入文件的时候注意&#xff1a;名字短的这个是&#xff0c;或者你打开后看哪个里面有800&#xff0c;因为我的激光重频是1.25Hz&#xff08;应该是&#xff0c;不太确定单位是KHz还是MHz&#xff09;&#xff0c;所以对应的时间是800s。 3.选中两列直接…

Mybatis框架进阶(标签)

1. <if>标签 DROP DATABASE IF EXISTS mybatis_test; CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4; use mybatis_test;DROP TABLE IF EXISTS user_info; CREATE TABLE user_info (id INT ( 11 ) NOT NULL AUTO_INCREMENT,username VARCHAR ( 127 ) NOT…

【知识点】图与图论入门

何为图论 见名知意&#xff0c;图论 (Graph Theory) 就是研究 图 (Graph) 的数学理论和方法。图是一种抽象的数据结构&#xff0c;由 节点 (Node) 和 连接这些节点的 边 (Edge) 组成。图论在计算机科学、网络分析、物流、社会网络分析等领域有广泛的应用。 如下&#xff0c;这…

泷羽sec-burp(4)burp常见用法 以及 漏洞测试理论 学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

Linux上传代码的步骤与注意事项

最近因为工作需要&#xff0c;要上传代码到 DPDK 上&#xff0c;代码已经上传成功&#xff0c;记录一下过程&#xff0c;给大家提供一个参考。我这次需要上传的是pmd&#xff0c;即poll mode driver。 1 Coding Style 要上传代码&#xff0c;第一件事就是需要知道Coding Styl…

vllm0.5.0的v1/completions各参数说明

一、调用示例 curl -X POST \http://ip:8001/v1/completions \-H accept: application/json \-H Content-Type: application/json \-d {"model": "qwen-api","prompt": ["讲个中文笑话"],"best_of": 1,"n": 1,&qu…

Java项目实战II基于微信小程序的作品集展示(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着移动互联网技术的飞速…

物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024

教程介绍 本次教程主要讲述如何下载与配置Arduino&#xff0c;以及开发版对应驱动的下载安装 原文链接&#xff1a;物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024 步骤概述 1&#xff1a;下载Arduino 2&#xff1a;安装Arduino 3&#xff1a;下载安装驱动 4&am…

13.在 Vue 3 中使用OpenLayers加载鹰眼控件示例教程

在 WebGIS 开发中&#xff0c;鹰眼控件 是一个常用的功能&#xff0c;它可以为用户提供当前地图位置的概览&#xff0c;帮助更好地定位和导航。在本文中&#xff0c;我们将基于 Vue 3 的 Composition API 和 OpenLayers&#xff0c;创建一个简单的鹰眼控件示例。 效果预览 在最…

Flink如何基于数据版本使用最新离线数据

业务场景 假设批量有一张商户表&#xff0c;表字段中有商户名称和商户分类两个字段。 批量需要将最新的商户名称和分类的映射关系推到hbase供实时使用。 原实现方案 a.原方案内容 为解决批量晚批问题&#xff0c;批量推送hbase表时一份数据产生两类rowkey&#xff1a;T-1和…

从GCC源码分析C语言编译原理——源码表层分析(脚本篇)

目录 一、目录结构 二、有意思的小功能 三、install脚本 脚本变量和设置 程序名称变量 模式和命令 参数解析 主要逻辑 四、主要功能脚本 ------------------------------------------------------------------------------------------------------------------------…

Latex转word(docx)或者说PDF转word 一个相对靠谱的方式

0. 前言 投文章过程中总会有各种各样的要求&#xff0c;其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中&#xff0c;其中公式转换是一个头疼的地方&#xff0c;还有很多图表&#xff0c;格式等等&#xff0c;想想就让人头疼欲裂。实践中摸索出一条相对靠…

挑战用React封装100个组件【010】

Hello&#xff0c;大家好&#xff0c;今天我挑战的组件是这样的&#xff01; 今天这个组件是一个打卡成功&#xff0c;或者获得徽章后的组件。点击按钮后&#xff0c;会弹出礼花。项目中的勋章是我通过AI生成的&#xff0c;还是很厉害的哈&#xff01;稍微抠图直接使用。最后面…

企业实践|广州新华学院携手泰迪智能科技开展大数据开发企业实践圆满结束

12月3日&#xff0c;新华学院健康学院携手广东泰迪智能科技股份有限公司联合开展大数据开发企业实践活动圆满结束&#xff0c;健康学院专业老师陈键聪及来自信息资源管理专业2023级24名学生参与此次活动结业仪式。泰迪智能科技董事长张良均、校企合作经理吴桂锋、钟秋平出席。 …

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式 面向对象设计原则概述 单一职责原则 单一职责…

深度和法线纹理

屏幕后期处理效果的基本原理就是当游戏画面渲染完毕后通过获取到该画面的信息进行额外的效果处理 之前的边缘检测、高斯模糊、Bloom、运动模糊等效果都是基于获取当前屏幕图像中的像素信息进行后期处理的 如果仅仅根据像素信息来进行一些效果处理&#xff0c;存在以下问题&…

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…

ORCA:基于持续批处理的LLM推理性能优化技术详解

大语言模型(LLMs)推理过程中的批处理优化面临显著挑战&#xff0c;这主要源于其推理过程的迭代特性。核心问题在于批处理中的各个请求完成时间存在差异&#xff0c;这导致资源释放和新请求整合的复杂性显著提高&#xff0c;特别是在处理不同完成阶段的请求时。当批处理中序列的…