vector模板的简易实现

news2025/1/17 6:09:07

这篇文章,我们模拟一下STL里面的vector的实现。但是会简化一些内容,让大家能够更好的理解。模拟实现的目的不是为了更好的造轮子,而是为了更好的理解这些容器。
在这里插入图片描述

文章目录

  • 1. 成员变量
  • 2. push_back函数
  • 3. reserve函数
  • 4. pop_back函数和下标运算符重载
  • 5. resize函数
  • 6. insert函数
  • 7. erase函数
  • 8. Linux下迭代器失效问题
  • 9. 构造函数和析构函数和赋值函数
  • 10. 使用memcpy拷贝问题

1. 成员变量

在前面的string里面,我们定义的成员变量是一个char类型的指针,然后是_size和_capacity,那么在vector里面是这样来定义的呢?我们可以看看vector的源代码。
在vector的源代码里它的成员变量是这样来定义的:
在这里插入图片描述
那么这里的迭代器是什么呢?
在这里插入图片描述
这个迭代器的意思是:模板类型的指针
那么我们在来看看这个迭代器的实现:
在这里插入图片描述
然后我们再看一下这个函数:
在这里插入图片描述
它的开始是start,结束是finish。相减为元素的个数。说明start是指向开头的指针,finish是指向最后一个元素的下一个位置的指针
那么end_of_storage是什么意思呢?
在这里插入图片描述
从这个函数可以分析出:end_of_storage减去开始,为容量大小。所以end_of_storage是指向总容量的下一个位置的指针
如下所示:
在这里插入图片描述
这样,我们可以先写出一些大概框架了:
在这里插入图片描述

2. push_back函数

和前面写string的思路是差不多的:
在这里插入图片描述
先看需不需要扩容。如果需要,扩完容就尾插。所以我们还需要写一个扩容的函数。

3. reserve函数

在这里插入图片描述
这样写有没有什么问题呢?这两行是有一些问题的:
在这里插入图片描述
在上一行代码_start被改成tmp了,这个size是_finish - 新的_start,然后在加上新的_start,所以还是_finish,还是nullptr。下面一行也是同样的道理。所以我们可以这样做:
在这里插入图片描述
就是提前把元素个数记录一下。然后我们再测试一下:
在这里插入图片描述

4. pop_back函数和下标运算符重载

这两个非常简单,都不多说什么了。
pop_back函数:
在这里插入图片描述
下标运算符重载:
在这里插入图片描述

5. resize函数

我们看一下库里的这个函数:
在这里插入图片描述
size_type是它自己typedef的,意思是:无符号整型。value_type也是重命名的,它的意思是:模板类型。那么我们自己写可以这样写:
在这里插入图片描述
第二个参数代表的是什么意思呢?就是我们不给参数的话,它会使用这个缺省值。而这个缺省值的意思是:创建一个临时对象,然后拷贝构造val。
那么有的同学会说:自定义类型具有构造函数,那么内置类型像int,double等类型咋办呢?
其实,C++中内置类型升级了,也可以认为有构造函数,析构函数。这样才能更好支持模板
在这里插入图片描述
剩下的思路和string哪里也是差不多的:
在这里插入图片描述

6. insert函数

我们先看一下库里的函数:
在这里插入图片描述
这里的pos的类型和返回值的类型是迭代器。为什么这里的返回值用的是迭代器呢?我们先写一个不带返回值的来分析:
在这里插入图片描述
那么这样写它有没有什么问题呢?我们来看:
在这里插入图片描述
经过测试程序挂了,我们调试一下:
在这里插入图片描述
一开始的数据。
在这里插入图片描述
扩完容的数据。我们发现_start和_finish的地址都变了,而pos没有变。因为扩容是把原来的空间销毁开辟新的空间,_start和_finish更新,pos没有更新。这就会导致下面的循环越界访问。所以我们要更新一下pos。
在这里插入图片描述
然后我们再测试一下:
在这里插入图片描述
这里就没有问题了。我们在说一下返回值的问题:
我们在这组数据中偶数的位置插入20。
在这里插入图片描述
但是出现了断言的错误。为什么呢?我们调试观察:
在这里插入图片描述
此时it来到2的位置,观察_start和_finish的地址。
在这里插入图片描述
此时扩完容之后,_start和_finish的地址都变了,it成为野指针了。等到后面遇到4的时候,又需要插入了,但是此时的it位置不对,是野指针。就会出现越界访问了。
那么有的同学会说:我们前面不是把pos的位置进行更新了吗?原因是:它是值传递,函数内部改变不会影响外部(it)。那么有的同学会说那我们改成引用。这是不行的,原因是:因为如果我们想传const对象或者是一个临时对象就不行了。所以我们需要返回值。
库里面的是这样返回的:指向新插入的第一个元素的迭代器
在这里插入图片描述
在这里插入图片描述
此时,我们每次插入把it更新一下。再测试一下:
在这里插入图片描述

7. erase函数

一般vector删除数据,都不考虑缩容的方案。缩容方案:size<capacity/2时,可以考虑开一个size()大小的空间,拷贝数据,释放旧空间。缩容方案的本质是时间换空间。一般设计都不会考虑缩容。
那么不考虑缩容,我们erase要不要返回值呢?
我们看一下库里面的erase函数的格式:
在这里插入图片描述
它的返回值:
在这里插入图片描述
指向函数调用删除的最后一个元素后面的元素的新位置的迭代器。如果操作擦除了序列中的最后一个元素,则此操作为容器结束

为什么呢?我们来分析一下:
在这里插入图片描述
在这里,我们使用的是库里面的vector的erase函数。我们删除的是序列中的最后一个元素。那么此时pos指向的就是_finish,我们下面再去访问就会越界。这时有的同学会说如果我们不删除最后一个元素呢?
在这里插入图片描述
我们发现程序还是挂了。
这是因为:erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了

我们来实现一下erase函数:
在这里插入图片描述
这里的pos位置就是删除元素的下一个位置。
我们这里的实现,并没有像vs那样强制检查pos位置。所以不会出现程序崩溃的情况。

总结:vector迭代器失效有两种。1.增容,缩容,导致野指针失效。2.迭代器指向的位置意义变了。

8. Linux下迭代器失效问题

Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

我们先看insert扩容情况下
在这里插入图片描述
在这里插入图片描述
扩容之后,迭代器已经失效了,在vs下程序就直接崩溃了,但是linux下不会。虽然能运行,但是输出的结果是不对的

erase删除情况下
在这里插入图片描述
在这里插入图片描述
erase删除任意位置代码后,linux下迭代器并没有失效。因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的

总结:对于insert和erase造成的迭代器失效问题。Linux的g++检查不严,基本依靠操作系统自身野指针越界检查机制。windows下vs系列检查更严格,使用一些强制检查机制,意义变了也可能会检查出来。

9. 构造函数和析构函数和赋值函数

析构函数比较简单
在这里插入图片描述
构造函数
上面我们已经写了一个无参的构造函数。但是它还有一些其它的构造方式,我们先来看看库的构造函数:
在这里插入图片描述
库里面的构造函数有三种。第一种的就是无参的(暂时不考虑分配器 )。那么我们还有两种的构造方式。
第二个是:构造n个val值的vector
在这里插入图片描述
然后我们再来测试一下:
在这里插入图片描述
第三个是:用迭代器区间来构造vector
在这里插入图片描述
我们来测试一下:
在这里插入图片描述
我们发现没有问题。但是我们再测试一下test8()。
在这里插入图片描述
我们看到报错了。这是为什么呢?原因是:10是int类型,而我们写的第二种是size_t类型,不太符合(有的编译器会类型转换,vs2019没有)。所以vs2019中就不会匹配第二种构造,而是去匹配第三种构造。而第三种构造中,10是int类型,*first是不能解引用int类型的。所以报错了。那么我们该怎么办呢?我们可以再重载一个构造函数:
在这里插入图片描述
我们重载一个int类型的,其它不变。就可以了。

拷贝构造函数
在这里插入图片描述
拷贝构造函数,我们这里就不写传统写法了。直接写现代写法:
在这里插入图片描述
赋值函数
在这里插入图片描述
这里我们也只写现代写法:
在这里插入图片描述
我们再测试一下:
在这里插入图片描述

10. 使用memcpy拷贝问题

模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?
在这里插入图片描述
我们这里写了一个杨辉三角的类。创建了一个5行的杨辉三角。我们来运行一下:
在这里插入图片描述
我们看到,它出现了随机数。这是什么情况呢?
在这里插入图片描述
其实这一步,要进行两次的拷贝构造。但是在某些编译器的优化下,可能进行一次拷贝构造。但是还是需要进行拷贝的。然后,我们调试进入拷贝构造里面观察。
在这里插入图片描述
此时,我们再进入构造函数里面观察。这个构造函数是由迭代器区间来构造的,所以进去的是这个构造函数:
在这里插入图片描述
然后,我们需要进入push_back函数。
在这里插入图片描述
因为第一次我们是没有空间的,直接开辟4个大小的空间。然后将前4行数据一个一个插入到tmp空间里。如下图所示:
在这里插入图片描述
但是当我们插入第5行数据时,需要扩容。
在这里插入图片描述
此时,我们已经开辟了一个大小为5的空间。memcpy的拷贝实际是浅拷贝,所以两个指向的是同一块空间。
在这里插入图片描述
此时又要delete[] _start,所以它会析构里面的vector< int >,而自定义类型销毁时会去调用它的析构函数,所以都会被析构。然后_start和_finish指向新的空间:
在这里插入图片描述
此时再插入第5行数据。
在这里插入图片描述
但是,我们要知道。前面的4行数据已经被销毁,成为随机值了。
在这里插入图片描述
此时tmp再和我们要拷贝构造的对象(ret)交换。
在这里插入图片描述
当这个函数结束时,ret也会销毁,销毁就会去调用析构函数。所以会报错。
解决方案:
在这里插入图片描述
这样赋值就不会出现问题了。如果是内置类型,那么直接赋值,肯定没有问题。如果是自定义类型不带资源管理的,那么赋值为浅拷贝也是可以的。如果是自定义类型带资源管理的,那么赋值就会用深拷贝。

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

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

相关文章

QT 多线程中使用QCanBusDevice进行PCAN通讯时,无法正常发出数据

QT 多线程中使用QCanBusDevice进行PCAN通讯时&#xff0c;无法正常发出数据 前言 我一开始的代码逻辑是&#xff0c;PCAN开启、关闭、发送、接收这些功能整合在一个工具类中&#xff0c;这个工具类的对象是在主线程创建的&#xff0c;然后我有一个要循环定时发送的功能是独立…

与企企通强强联手!哈尔斯二期数字化采购项目正式启动

近日&#xff0c;浙江哈尔斯真空器皿股份有限公司&#xff08;以下简称“哈尔斯”&#xff09;联合企企通举办二期数字化采购项目启动会&#xff0c;旨在助力哈尔斯实现采购数字化全面升级&#xff0c;提升自主品牌竞争力。会上&#xff0c;双方就该项目的建设方案、项目资源、…

铝合金表面处理废水除铝工艺

铝型材表面处理用水量大&#xff0c;产生废水多&#xff0c;废水中有害物质持续排放。如不加以处理必将污染环境。同时伴随着我国对排污量的征税&#xff0c;也会增加企业的成本和负担。因此&#xff0c;从企业的社会责任和效益两方面考虑&#xff0c;进行废水处理是必须和必要…

解决VsCode启动Vue项目报错:‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

问题描述 最近居家办公&#xff0c;网速不太稳定&#xff0c;开会的时候网络也是断断续续的&#xff0c;今天需要拉下前端项目运行起来 在我执行npm i下载包的时候&#xff0c;我看到网络超时的错误警告就感觉不太秒。知道大概率要启动失败了 果不其然执行npm run serve的时…

窃取信息的新恶意软件通过假冒的破解网站感染使用者

©网络研究院 一种名为“RisePro”的新型信息窃取恶意软件正在通过由 PrivateLoader 按安装付费 (PPI) 恶意软件分发服务运营的虚假破解站点进行分发。 RisePro 旨在帮助攻击者从受感染的设备中窃取受害者的信用卡、密码和加密钱包。 本周Flashpoint 和 Sekoia的分析师发…

前端框架 Nuxt3 集成 Pinia

目录 一、Nuxt3集成Pinia 二、Pinia的使用 state的使用 1、基本使用及动态渲染 2、state的重置 3、批量更改state数据 getters的使用 1、getters的基本使用 2、getters传参 actions的使用 1、actions的基本使用 一、Nuxt3集成Pinia 参考官方文档&#xff1a;简介 |…

【JavaSE】常用类(447~515)

String 447.常用类-每天一考 1.画图说明线程的生命周期&#xff0c;以及各状态切换使用到的方法等 状态&#xff0c;方法 2.同步代码块中涉及到同步监视器和共享数据&#xff0c;谈谈你对同步监视器和共享数据的理解&#xff0c;以及注意点。 synchronized(同步监视器){//操…

消息队列RabbitMQ学习笔记(五)高级特性

1. 发布确认高级 在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始思考&#xff0c;如何才能进行 RabbitMQ 的消息可靠投…

ccc-sklearn-11-线性回归(1)

1.线性回归概述 回归需求在现实中非常多&#xff0c;自然也有了各种回归算法。最著名的就是线性回归和逻辑回归&#xff0c;衍生出了岭回归、Lasso、弹性网&#xff0c;以及分类算法改进后的回归&#xff0c;如回归树、随机森林回归、支持向量回归等&#xff0c;一切基于特征预…

自定义卷积实现卷积的重参数【手撕代码】

在我的上篇文章中主要对RepVGG进行了解析【RepVGG网络中重参化网络结构解读】&#xff0c;里面详细的对论文中的代码进行了解析&#xff0c;展示了RepVGG在重参数时是如何将训练分支进行合并的&#xff0c;总的一句话就是在推理阶段&#xff0c;会将1x1以及identity分支以paddi…

vivo 游戏中心低代码平台的提效秘诀

作者&#xff1a;vivo 互联网服务器团队- Chen Wenyang 本文根据陈文洋老师在“2022 vivo开发者大会"现场演讲内容整理而成。公众号回复【2022 VDC】获取互联网技术分会场议题相关资料。 在互联网流量见顶和用户需求分层的背景下&#xff0c;如何快速迭代产品功能&#xf…

函数模板-C11/17/14

函数模板 文章目录函数模板定义函数模板使用函数模板样例两阶段翻译 Two-Phase Translation模板的编译和链接问题多模板参数引入额外模板参数作为返回值类型让编译器自己找出返回值类型将返回值声明为两个模板参数的公共类型样例默认模板参数样例重载函数模板模板函数特化非类型…

cocoapods的使用

swift开发之cocoapods的使用 之前介绍了cocoapods的使用&#xff0c;我们可以知道通过pod search XXX(三方依赖库名称)可以就搜索到想要的第三方是否存在。 这次主要简单介绍cocoapods如何引入第三方库的,以BluetoothKit为例。 首先&#xff0c;我们终端中通过cd命令定位到要…

二十二、shiro安全框架基础

一、简介 1. shiro简介 Apache Shiro 是 Java 的一个安全&#xff08;权限&#xff09;框架。Shiro 可以非常容易的开发出足够好的应用&#xff0c;其不仅可以用在JavaSE 环境&#xff0c;也可以用在 JavaEE 环境。Shiro 可以完成&#xff1a;认证、授权、加密、会话管理、与…

“智慧”控漏 削减产销差-城镇供水管网分区计量管理系统

平升电子城镇供水管网分区计量管理系统根据国际国内分区计量的要求和标准研发&#xff0c;专门针对水司漏损控制和产销差管理而设计。系统涵盖分区管理、管网流量和压力监控、水量统计分析、产销差分析、漏损评估、夜间最小流量分析、用水异常报警等功能。核心目标是找到整个管…

ReactJS入门

目录 一&#xff1a;前端开发的演变 二&#xff1a;ReactJS简介 三&#xff1a;搭建环境 四&#xff1a;React快速入门 一&#xff1a;前端开发的演变 到目前为止&#xff0c;前端的开发经历了四个阶段&#xff0c;目前处于第四个阶段。这四个阶段分别是&#xff1a; 阶段一…

equals()与hashcode()之间的关系

1、equals简介 被用来检测两个对象是否相等&#xff0c;即两个对象的内容是否相等&#xff1b; equals 方法&#xff08;是String类从它的超类Object中继承的&#xff09;用于比较引用和比较基本数据类型时具有不同的功能&#xff1a; 比较基本数据类型&#xff0c;如果两个值…

马哥SRE第11周课程作业

ansible role zabbix相关话题1. ansible 常用指令总结&#xff0c;并附有相关示例。1.1 Ansible相关工具1.1.1 ansible-doc1.1.2 ansible 命令用法1.1.3 ansible-console1.1.4 ansible-playbook1.1.5 ansible-vault1.1.5 ansible-galaxy2. 总结ansible playbook目录结构及文件用…

javaee之Spring4

之前说到AccountDao需要继承JdbcDaoSupport这个类&#xff0c;那么现在来看一下这个类的内容 JdbcDaoSupport.java package com.itheima.dao.impl;/*** 此类用于抽取dao中的重复代码 */public class JdbcDaoSupport {private JdbcTemplate jdbcTemplate;public void setJdbcT…

人大金仓数据库备份应用sys_dump的使用

人大金仓数据库软件给数据库管理员用户提供了管理维护数据库的多个客户端应用&#xff0c;更多参考&#xff1a;《KingbaseES客户端应用参考手册》。 我们可以看到备份的应用有两个&#xff1a; 1、sys_dump:将KingbaseES数据库备份为一个脚本文件或者其他归档文件 2、sys_d…