vector常用的接口和底层

news2025/4/16 14:58:28

一.vector的构造函数

        我们都是只讲常用的。

        

        这四个都是比较常用的。

        第一个简单来看就是无参构造,是通过一个无参的对象来对我们的对象进行初始化的,第一个我们常用来当无参构造来使用。

        

        第二个我们常用的就是通过多个相同的数字来初始化一个vector。

        

        像这样。

        第三个的主要用途就是我们下图的形式。

        

        它会通过从头遍历到结束来初始化我们的vector。

        第四个毫无疑问就是通过另外一个vector对象来初始化我们的对象。

二.push_back

        这个就是用来插入数据的。

        

        就是在v1的尾部插入1和2,这些都很简单。

三.reserve

        我们来探究一下它的扩容机制吧,看看和string的是否相同。

        

        

        还是1.5倍的扩容机制,reserve就是预留一个空间,减少扩容次数。

   剩下这些方法都和string都是相同的,大家感兴趣了可以自己尝试谢谢。

四.vector<vector<T>>

        接下来我们用一个例题来讲解一下这个不好理解的知识点,同时也是非常重要 的,话不多说,我们直接上题。

        

        想必杨辉三角这道题大家非常的熟悉,但是我们用c语言写会变得非常麻烦,我们可以看一下力扣给的c语言的函数就知道了。

       

  • numRows:类型为 int ,用于指定要生成的杨辉三角的行数。它明确了程序需要生成的杨辉三角规模大小 ,是后续内存分配、元素计算等操作的基础。比如传入 5 ,就表示要生成杨辉三角的前 5 行。

  • returnSize:类型为 int* ,是一个指针,用于返回实际生成的杨辉三角的行数。在函数内部,通常会将之前传入的 numRows 值赋给这个指针所指向的内存空间 ,以此告知调用者函数实际生成了多少行杨辉三角数据。例如函数执行完,*returnSize 的值就是实际生成的行数,方便调用者后续处理返回结果。

  • returnColumnSizes:类型为 int** ,是一个二级指针。它用于返回杨辉三角每一行的列数信息。因为杨辉三角每一行的元素个数不同,通过这个二级指针,函数可以将每一行对应的列数存储起来并返回给调用者 。比如 returnColumnSizes[0] 指向的内存空间存储第 0 行的列数,returnColumnSizes[1] 指向的内存空间存储第 1 行的列数,以此类推,方便调用者准确获取每一行的元素数量,正确解析返回的杨辉三角数据。

        况且我们的二维数组的初始化也不支持用变量初始化,导致我们需要动态开辟内存,这是非常麻烦的。

        但是如果我们使用cpp的vector<vector<T>>这些难点都会迎刃而解了。

        我们先来看一下这个的原理。

        

        我们看一下这个,需要从下往上看,一层套着一层,通过函数模板给它分配类型。

        你可以理解为第一层就是指向这三个对象的指针,和二位数组很像,第一层就是通过这个vector<int>*类型的指针,来找到我们需要访问到的对象,然后再对其进行操作。

        我们的这个也是可以通过[][]的形式访问的,但是和二维数组不同,静态二维数组的底层还是一维数组,

        它就是通过下面的那个公式进行解引用来访问的,但是我们的vector则不是,我们的这个是通过调用两次函数来访问的,其实底层原理还是指针。

        

        调用原理就是我们这个图中浅蓝色的代码,先通过下面的第一层找到并返回我们需要的vector对象,然后再通过这个对象再次调用[]这个运算符再返回我们所要的数据。

        这就是我们c++来解决这个问题的代码了,就是先创建一个类似二维数组的一个vv对象,然后通过空参构造初始化一下,最后通过resize先把全部的数据初始化为,然后再按照正常的步骤做就行了,只需要传一个参数,更加的简单易懂。

五.insert

        只讲一个用法,就是通过迭代器插入。

        

        一个表示在头进行插入,一个表示在3的位置插入。

六.vector<char>

        

        现在看着代码我们有一个问题,我们能否用这个vector<char>来代替string呢?

        答案是肯定不能的。

        vector<char>结尾是没有\0的不能兼容c语言,但是string是有\0的,可以兼容c语言。

        这就是它们最大的区别。

七.reverse

        这个是逆置函数,我们来看一下它的底层。

        

        看到这个代码,我们有一个问题,我们能否把while循环中的条件改为last>first呢?

        肯定是不行的,如果是顺序表,空间是连续的还可以,但是如果是链表呢,指针的比较实质上是地址大小的比较,链表的地址并不连续,后面的地址不一定比前面大,所以是不行的。

        为什么是first!=--last呢?

        我们看下面这个图就可以理解了。

        

        last指向最后一个的下一个位置。

八.vector<string>

        

        你可以理解为 底层就是一个指针指向这七个string类型的空间,类似于数组,我们用空参构造初始化了这七个,然后通过append函数来给第二个,下标为1的string对象赋值。

        底层就是string* 的指针。

        我们来分析一下它的范围for。

        

        如果我们用正常的范围for,它每次都需要经过深拷贝给e,代价太大了,我们该如何解决这个问题呢?

        

        我们可以这样,使用引用,就减少了深拷贝,提高了效率。

九.底层原理的实现

        9.1 构造函数和成员变量

        下面我们来实现一下。

        它的底层和string是有区别的,vector的底层是通过三个迭代器来完成的。

        

        就是这样完成的,这是三个成员变量。

        下面我们来写一下构造和析构函数。

        

        因为三个指针都指向同一块空间,所以只需要delete _start即可,构造就是把这三个成员变量全部置为空。

        这样我们就完成了简单的前期工作了。

9.2 size()和capacity()

        因为vector和string底层的不同,我们这里并没有直接用两个变量来存放size和capacity,而是使用了两个函数来完成,大小和空间大小的获取。

        

        _start永远指向首位置,_finish指向_start+size()的位置,_end指向_start+capacity()的位置,所以我们可以通过指针的相减,直接获得元素的个数和空间的大小,这两个函数也是很好理解的。

9.3 reserve()

        这个函数和string的一样,主要作用就是扩容的。

        

        就是这样完成的,通过开辟一块新的空间让tmp指向它,然后再将旧空间的内容拷贝过来,最后释放旧空间指向新空间即可,我们来解释一下这个oldsize的作用,如果没有oldsize的话,因为我们的这三个指针开始均为空,地址为0x000000,当我们的_start指向新的地址之后,它的地址就会改变(假设为0x000100),此时如果我们让_finish=_start+size()的话,因为这里的_start的地址是0x000100,此时相减之后,size()就会是100,此时和我们的原来的大小大概率是不相同的,此时就会出错了。

        就是这个样子。

        这样子这个函数就完成了。

9.4 push_back()

        

        这个函数就简单了,如果空间不够就扩容就行了,然后通过指针的解引用给指向的空间赋值即可,很简单的函数。

9.5 begin()和end()

        这两个函数是实现范围for的必要函数,实现起来也很简单。

        

        就是这样的,很简单。

9.6 operator[]

        想要像数组一样访问元素,这个[]运算符是必不可少的。

        

        就是这样简单的实现。

        我们先来测试一下我们写的代码。

        

        

        我们发现是符合我们的预期的。

        9.7 pop_back

        

        这个函数的作用就是尾删元素的,直接对_finish--就可以了,让访问不到就行,不用把空间释放了。

        9.8 insert

                这个是通过迭代器版本的插入,这个用的是比较多的,想在哪里插入,直接让迭代器加上距离就行了,我们来看一下下面的代码。

        

        我们发现此时出现了问题,我们把它称为迭代器的失效,我们来研究一下为什么会失效呢?

        我们在插入四个数据之后,此时通过insert插入的第五个数据,此时就要扩容了,应该是这里出现了问题,我们来分析一下。

        

        这是扩容前的场景,我们再来看一下扩容之后的情景吧。        

        

        扩容之后,你的it还是指向原来的空间,但是你的_start还是指向原来的旧空间,此时_finish和it就不在同一块空间内,这里应该是it的空间大于end指向的空间地址,因此没有进入insert函数的while循环当中,直接++_finish了,此时第五个数就是随机数了。

        这就是迭代器失效的一种情况。

        修正方案:

        

        我们这样修改就不会出现这种情况了,我们让it也指向了它在新空间内的位置,此时就没问题了。

        

        再来看一下这个情况,还是在扩容的时候插入,此时我们是通过一个迭代器表示我们要插入的位置,然后我们运行一下看看。

        

        思考一下,此时我们的*it为什么会是随机数呢?

        这是因为,insert的第一个参数是传值传参,传入的是副本,此时那个副本进入了新的空间,释放了旧空间,此时这个it还是在原来的地方指着没有变,所以就出现了这个情况,空间被释放出现了野指针。

        

        就是这样的,it指向的空间被释放了,所以是野指针,那么有人就说了,我们可以通过传引用来完成这个操作啊。

        

        此时我们无法传入一个常量内容了,也不方便,所以,你只需要记住,这里的it是野指针就行。

        9.9 erase

        

        这个就是我们的erase函数,这个函数的作用就是删除指定位置的元素,我们通过找到pos+1位置的元素向前覆盖就可以完成了。

        我们完成了这个函数,此时我们来做一个题

        删除所有的偶数

        这就是我们的代码实现了,我们看上去没有错误,但是我们来运行一下。

        

        我们发现程序崩溃了,这是什么原因呢?

        我们来分析一下。

                        

        我们发现了两个问题,第一个就是我们没有检查3这个数字是否为偶数就直接去4那里了,但是这也不是崩溃的原因,原因就是

        在第二个assert处程序崩溃了。

        开始的时候_finish是在4的后面那个位置的,但是it到达2处,此时调用erase,此时2被3覆盖,3被4覆盖,4的位置的值不动,此时_finish在第二个4的那个位置,此时it直接去到第一个4那里,继续走erase函数,此时 ,it就到第二个4的位置了,此时_finish在3的位置,然后it永远不会和_finish相等的,况且现在it还大于_finish,传入参数it就是pos,pos>_finish,所以断言触发了。

        我们现在就来解决这两个问题吧。

        

        这个代码的逻辑就很好解决了那两个问题,但是vs下还是跑不了,但是g++下能跑,这就是vs的一种机制了,对于迭代器的失效检查非常严格。

        vs认为,你的迭代器insert和erase之后就会失效,会改变标记,导致不让用。

        我们该怎么办呢?

        只需要加一个返回值就行了

        我们只需要把这个位置返回给it迭代器就会使它不失效了,每次更新一下it迭代器就可以使它不失效了。

        

9.10 resize

        

        两个参数,第二个参数给了一个缺省值,这里相当于给int和其他类型也升级了一个构造函数,如果T=int,那么T()就是0,这个函数实现起来也不难,和string的基本一样。

        

        分别在不同区间要干的事情。

        9.11 拷贝构造

        

        我们直接先开了一个空间,然后通过push_back赋值即可。

        vector(const vector& a);//这样也是ok的,在类里面可以直接写vector但是类外面不行,在类里面用vector的时候可以不写后面的泛型,这个看见了知道就行,尽量不要使用。

        

9.12 operator=

        

        这个是我们正常的实现流程,但是我们受到前面string中实现的启发,我们有更简便的方法来实现。

        

        反正这个函数中的形参vector<T> a是没有用的,直接交换一下空间的指向,通过析构也可以很好的释放旧空间,很方便。

        结合着上面的三个方法我们来写个代码。

        

        

        我们发现程序有崩溃了,这是怎么回事呢?

        这个主要的崩溃的点就在于vv=v2这条语句,执行这条语句就会进入到拷贝构造当中,因为我们要传值传参,就会调用拷贝构造生成一个副本,在生成这个副本的过程中,就出现了问题,我们来看一下。

        

        此时就会进入到reserve函数中。

        

        因为当我们的v2传给赋值运算符形参的时候,会调用拷贝构造,当调用拷贝构造的时候,进入了reserve函数,此时这里的形参封装的三个迭代器参数都是随机值,你此时调用capacity的时候就出现了问题,所以我们得初始化一下,因为我们的拷贝构造没有给初始化列表,所以形参的三个迭代器就是随机值。

        解决方案一:

        

        解决方案二:

        

        给缺省值,我更推荐这个方案,好处就是给了缺省值,不管是调用拷贝构造还是构造函数,都会通过给的缺省值走初始化列表,这样就不会导致随机值的情况了。

        这样问题就得到完美的解决了。

        

        9.13 vector<string>

        

        我们看一下这个代码,打印四个不牵扯扩容的时候是没有任何问题的,但是打印第五个的时候出现了问题,我们来看一下。

        

        出现了乱码,我们来解决一下这个问题,其实这个问题不是太容易发现,我们肯定知道是扩容的时候出现的问题,我们来看一下。

        程序崩溃的主要原因在于自定义 vector 类的 reserve 方法中使用了 memcpy 来复制对象。memcpy 是一个按字节复制的函数,它只复制对象的二进制内容,不会调用对象的复制构造函数。当 T 是像 std::string 这样的复杂类型时,使用 memcpy 复制会导致浅拷贝问题,使得多个对象共享同一块内存,在析构时会造成重复释放内存,从而引发程序崩溃。

        导致我们两个空间内的前四条数据指向的是同一块内容,当我们的上面那一块空间被delete了,这是下面的前四条数据也会被delete,此时就出现了乱码的情况,第五个不受影响。

        我们该怎么解决这个问题呢?

        

        这样就可以了。

        当是string的时候这就是通过string的赋值运算符=来完成的,这个运算符返回的是string的值,而不是其他东西,其他的类型也可以通过这个来赋值。

9.14 再探构造

        

        std库里面有一个这样初始化的形式,接下来我们也来实现一下吧。

        

        它就是通过一个initializer_list<T>参数来完成的,你可以把它理解为开创了一个数组,两个指针,一个指向头,一个指向尾部,它和数组很相似,都是在栈上开辟了空间我们可以看一下。

        

        发现它们两个的地址很相似,这个不必深究,会用就行。

        这个构造的实现就是模板套模板的形式,模板类里面再弄一个模板函数,我们下面来看一下它的使用。

        

        a就是上面定义的数组,我们可以看到功能非常的强大,可以使用迭代器,也可以存入一个字符串类型的,也可以用数组初始化,功能很强大。

        接下来就是最后一个了。

        就是用100个0初始化那样的形式。

        

        就是这样实现的很简单,接下来我们来用一下。

        

        

        这个结果没问题,我们再把下面的注释拿掉再来看一下。

        

        我们发现报错了,怎么回事呢?

        我们发现当我们使用两个int类型的时候,它会调用到上面的我们的那个模板,因为没有非常匹配的,所以编译器就选择了模板。

        

        一个调用上面的,一个调用下面的,你对int类型的数解引用,所以就报错了,我们该如何解决呢?

        有人可能会把size_t改成int,但是改成int之后,我们要是再想使用size_t类型的呢,它还是会匹配到上面,索性两个都留下就没问题了。

        

        此时就没有问题了。

        到这里vector就结束了。

        

十.结束语

        

         感谢大家的查看,希望可以帮助到大家,做的不是太好还请见谅,其中有什么不懂的可以留言询问,我都会一一回答。  感谢大家的一键三连。

       

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

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

相关文章

【2025年3月中科院1区SCI】Rating entropy等级熵及5种多尺度,特征提取、故障诊断新方法!

引言 2025年3月&#xff0c;研究者在国际机械领域顶级期刊《Mechanical Systems and Signal Processing》&#xff08;JCR 1区&#xff0c;中科院1区 Top&#xff0c;IF&#xff1a;7.9&#xff09;上以“Rating entropy and its multivariate version”为题发表科学研究成果。…

【AI学习】李宏毅老师讲AI Agent摘要

在b站听了李宏毅2025最新的AI Agent教程&#xff0c;简单易懂&#xff0c;而且紧跟发展&#xff0c;有大量最新的研究进展。 教程中引用了大量论文&#xff0c;为了方便将来阅读相关论文&#xff0c;进一步深入理解&#xff0c;做了截屏纪录。 同时也做一下分享。 根据经验调整…

Nacos-Controller 2.0:使用 Nacos 高效管理你的 K8s 配置

作者&#xff1a;濯光、翼严 Kubernetes 配置管理的局限 目前&#xff0c;在 Kubernetes 集群中&#xff0c;配置管理主要通过 ConfigMap 和 Secret 来实现。这两种资源允许用户将配置信息通过环境变量或者文件等方式&#xff0c;注入到 Pod 中。尽管 Kubernetes 提供了这些强…

【BUG】Redis RDB快照持久化及写操作禁止问题排查与解决

1 问题描述 在使用Redis 的过程中&#xff0c;遇到如下报错&#xff0c;错误信息是 “MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk...”&#xff0c;记录下问题排查过程。 2 问题排查与解决 该错误提示表明&#…

java分页实例

引言 在现代Web应用和移动应用中&#xff0c;面对大量数据的展示&#xff0c;分页技术成为了提升用户体验和优化数据加载效率的关键手段。尤其是在MySQL数据库环境中&#xff0c;合理运用分页查询不仅能显著减少服务器负载&#xff0c;还能提升数据访问速度&#xff0c;为用户提…

【Linux篇】ELF文件及其加载与动态链接机制

ELF文件及其加载与动态链接机制 一. EFL文件1.1 ELF文件结构二. ELF文件形成与加载2.1 ELF形成可执行2.2 ELF控制性文件的加载2.2.1总结 三. ELF加载与进程地址空间3.1 动态链接与动态库加载3.1.1 进程如何看到动态库 3.2 全局偏移量表GOT(global offset table&#xff09;3.2.…

经典算法 判断一个图中是否有环

判断一个图中是否有环 问题描述 给一个以0 0结尾的整数对列表&#xff0c;除0 0外的每两个整数表示一条连接了这两个节点的边。假设节点编号不超过100000大于0。你只要判断由这些节点和边构成的图中是否存在环。存在输出YES&#xff0c;不存在输出NO。 输入样例1 6 8 5 3 …

AI与深度伪造技术:如何识别和防范AI生成的假视频和假音频?

引言&#xff1a;深度伪造的崛起 近年来&#xff0c;人工智能技术迅猛发展&#xff0c;其中深度伪造&#xff08;Deepfake&#xff09; 技术尤为引人注目。这项技术利用深度学习和神经网络&#xff0c;可以轻松生成高度逼真的假视频和假音频&#xff0c;使人物的面部表情、语音…

ESP32驱动读取ADXL345三轴加速度传感器实时数据

ESP32读取ADXL345三轴加速度传感器实时数据 ADXL345三轴加速度传感器简介ADXL345模块原理图与引脚说明ESP32读取ADXL345程序实验结果 ADXL345三轴加速度传感器简介 ADXL345是一款由Analog Devices公司推出的三轴数字加速度计&#xff0c;分辨率高(13位)&#xff0c;测量范围达…

【Linux】系统入门

【Linux】系统初识 起源开源 闭源版本内核内核编号 Linux的安装双系统(不推荐)WindowsLinuxvmware虚拟机vitualbox操作系统的镜像centos 7/ubuntu云服务器租用 Linux的操作lsmkdir 文件名pwdadduser userdel -rrm文件名cat /proc/cpuinfolinux支持编程vim code.c./a.out 运行程…

github配置ssh,全程CV

1)随便找一个文件夹右键进入git bash 2)验证是否已有公私钥文件 cd ~/.ssh ls如果不存在则生成然后获取 生成时一直回车 ssh-keygen -t rsa -C "xxxxxx.com" cd ~/.ssh cat id_rsa.pub如果存在则直接获取 cd ~/.ssh cat id_rsa.pub3&#xff09;复制 4&#xf…

Dify简介:从架构到部署与应用解析

Dify 是一个开源的生成式 AI 应用开发平台&#xff0c;融合了后端即服务&#xff08;Backend as a Service, BaaS&#xff09;和 LLMOps 的理念&#xff0c;旨在帮助开发者快速搭建生产级的生成式 AI 应用。本文将详细解析 Dify 的技术架构、部署流程以及实际应用场景&#xff…

碳化硅(SiC)功率模块方案对工商业储能变流器PCS市场格局的重构

碳化硅&#xff08;SiC&#xff09;模块方案&#xff08;如BMF240R12E2G3&#xff09;对工商业储能变流器PCS市场格局产生颠覆性的重构&#xff1a; 2025年&#xff0c;SiC模块方案&#xff08;如BMF240R12E2G3&#xff09;凭借效率、成本和政策支持的三重优势&#xff0c;将重…

Redis入门(Java中操作Redis)

目录 一 基础概念 1. Redis 核心特点 2. Redis 与 MySQL 的对比 3. Redis的开启与使用 二 Redis的常用数据类型 1 基础概念 2 数据结构的特点 三 Redis基础操作命令 1 字符串操作命令 2 哈希操作命令 3 列表操作命令 4 集合操作命令 5 有序集合操作命令 6 通用命令…

算法思想之位运算(一)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之位运算(一) 发布时间&#xff1a;2025.4.12 隶属专栏&#xff1a;算法 目录 算法介绍六大基础位运算符常用模板总结 例题位1的个数题目链接题目描述算法思路代码实现 比特位计数题目链接题目描述算法思路…

【基于Servlet技术处理表单】

文章目录 一、实验背景与目的二、实验设计与实现思路1. 功能架构2. 核心代码实现3. 测试用例 总结 一、实验背景与目的 本次实验旨在深入理解Servlet工作原理&#xff0c;掌握JSP与Servlet的协同开发&#xff0c;实现前端表单与后端数据处理的交互。具体目标包括&#xff1a;设…

[OS] mmap | fd是什么 | inode机制 | vfs封装

Linux 下一切皆文件 * 统统抽象为文件&#xff0c;系统封装一层结构体之后&#xff0c;通过指针来访问 * 文章后面的 几个思考题都挺好的 * 后面涉及到的inode 机制&#xff0c;去年暑假的这篇文章&#xff0c;有详细的记录到过 【Linux】(26) 详解磁盘与文件系统&#xff1a;从…

STL详解 - vector的模拟实现

目录 一、整体设计 1.1 核心结构 1.2 迭代器实现 二、核心接口实现 2.1 构造函数系列 &#x1f334;默认构造 &#x1f334;迭代器范围构造 &#x1f334;元素填充构造 2.2 拷贝控制 &#x1f335;拷贝构造函数 &#x1f335;赋值运算符&#xff08;现代写法&#xf…

C++第三方库【JSON】nlohman/json

文章目录 优势使用API从文件中读取json从json文本创建json对象直接创建并操作json对象字符串 <> json对象文件流 <> json对象从迭代器读取像使用STL一样的访问STL容器转化为 json数组STL容器 转 json对象自定义类型转化为 json对象 限制 优势 直观的语法&#xff…

超细的ollama下载以及本地部署deepseek项目

Ollama 是一个开源的本地化大语言模型&#xff08;LLM&#xff09;运行和部署工具&#xff0c;专注于让开发者能够快速、高效地在本地运行和管理各种开源大语言模型&#xff08;如 LLaMA、Mistral、GPT 系列等&#xff09;。它提供了一个统一的接口&#xff0c;简化了模型下载、…