vector 手动实现 及遇到的各种细节问题

之前对vector的一些功能使用了一下 接下来手动实现一下vector  vector的实现和string还是有不小区别的   有很多地方都有细节的问题

不同于string的成员变量一个指针一个size一个capacity的成员变量  vector里面存的是三个迭代器iterator  这的迭代器其实就是模版T的指针  这样就可以存各种类型的数据了

三个指针  statr指向第一个位置 finish指向最后一个元素的后一个位置  end_of_storage指向能存储容量的后一个位置

因为用到了模版 所以不能分两个文件来写 就都写到vector.h里面

接下来一步步实现vector的功能 在实际的过程中遇到了各种的问题 一步步分析并解决

容量操作

首先先实现简单的一些容量的接口

size和capacity只需要分别返回finish-start和_end_of_storage   empty只需要判断finish是否等于start

reserve

如果按照string那样的方式如下图的方式来写reserve会出问题

这里的三个成员变量都是指针  扩容开新空间释放原来空间之后 需要让三个成员变量进行更新 _start和_end_of_storage按照上面写的没有什么问题  但是_finish的更新需要依靠size这个函数如下  此时_finish是更新前的  而_start是更新后的 此时相减就出问题了

解决方法

1.  先更新_finish 再更新start  先更新finish的话 此时的finish和start都指向原来的空间 size可以正常算 那么finish的就可以指向更新后的位置了

2. finish更新位置需要用到size 那么在更新start之前就提前算一下size并存起来到old_size中

有了reserve之后 我们就可以写尾插的函数了  先判断空间满没有 满了先用reserve来扩容  然后进行尾插并更新finish 

 形参用引用是因为 用传值传参的方式会进行拷贝构造 如果传的是自定义类型的话拷贝代价比较大  同时如果实参传递的时候传的不是变量而是1  2 直接的数字这种方式 此时的实参具有常性 如果这里引用的不用const修饰的话 相当于权限放大了 会报错

然后再重载[]之后我们就可以用下标的方式进行打印了

如下实现四个迭代器之后 我们就可以通过迭代器的方式和范围for的方式进行打印了 这些都和之前一样

insert

在string那里insert插入我们实现的是下标的方式 在上次vector使用中我们发现标准库中的insert只有迭代器的方式 所以这里也用迭代器的方式

如果按下图的方式进行insert插入的操作就会出问题 之后再对pos其实出的问题和之前reserve中_finish更新的问题相似 不过这里是pos的问题

下面来分析一下为什么会出现上述的问题

在insert插入之前 如果空间不够需要先扩容  扩容操作通过reserve实现开新空间和将三个迭代器进行更新  但是pos没有更新还是指向之前空间的某个位置如下图

而且扩容操作会把原来的空间给释放掉  此时pos位置指向的是被是否掉的空间 类似野指针 *pos=x操作是在原来空间的位置进行的 新的空间数据将原来数据拷贝之后 但是finish正常进行了++ 就导致还是最终会多打印一个数据  但这个数据为随机数

问题解决

我们知道出问题的原因和reserve那里类似 pos的位置还指向原来的空间

所以我们需要对pos的位置进行更新  在reserve更新之前存pos和_start相减的值 通过reserve更新空间及三个迭代器之后 再更新pos的位置 这样就可以正常的运行了

find可以查找传的两个迭代器区间内传的第三个参数   并返回该位置的迭代器

迭代器失效

有了insert之外再结合find  我们就可以选择在想要的位置进行插入了 如下图 find找到2的位置并返回给了pos insert再根据这个pos进行插入 但是在插入之后将pos进行*10的操作 没有将2*10 而是新插入的6*10了

这还是刚刚类似的问题 在insert函数里面pos位置插入数据后pos指向的还是原来的位置 这时候这个位置的数据是新插入的6了  也就是之前的通过find得到的pos失效了 pos的意义改变了

另一种失效问题

如下图先尾插4个数据 再在2的位置插入一个6 再对pos位置*10 这次这里什么都没有变 这里是在insert函数里面空间不够通过reserve扩容了  扩容了之后pos指向的还是原来的旧空间  刚刚我们更新pos是在insert函数里面对形参pos进行的 对于实参pos还是指向旧的空间   这种的其实就类似野指针一样

所以pos再使用一次之后就不要再次直接使用了

实际上对于pos的二次使用标准库中的vector在vs下这样都会直接报错(如下图)   linux下gcc编译器不会报错 会和我们上面自己写的一样可以运行 

解决方法

错误方式

既然是在里面形参改变没有影响实参  那么如果采用加引用的方式呢

采取这种方式  对于右下这种正常的写法就不能使用了(函数返回的临时对象具有常性 这里传过来相当于权限放大)

正确解决方法   

insert返回pos+1的位置  pos每次使用之后更新它的位置  如下图就可以正常进行了 并符合我们的预期

在类外写一个Printf_vector函数 这里的x是被const修饰的  调用begin时候也是调用的const的版本返回值也为const类型的迭代器  

但是这样只能打印int类型的 所以这里可以用模版函数的方式 如下图

但是这里会报如下图的错误

这是因为在类外用域作用符::访问类里面时候 可能访问的是类型 也可能是类的静态成员变量 编译器不知道是什么  这是规定 没有实例化的类模板里面取东西,编译器不能区分这里const_iterator

解决方法

在前面加上typename就是告诉编译器我们访问的是类型  如果是要访问静态的那后面也不会有变量i

除了在前面加typename之外 也可以用auto来自动识别类型的方式解决

另外打印还可以如下 这样不只是vector可以打印 任意类型的容器用迭代器方式都可以打印

erase实现

如下图要用erase删除全部的偶数  迭代器it从begin开始每次判断该位置的值是不是偶数是就用erase删去然后it++  一直到最后一个位置   但是结果不和我们预期的一样 会有以下两种问题

第一个报错了  第二个没有删干净 

原因是这个代码如果该位置是偶数的话会把这个数给删掉然后后面的数在函数里面会向左移动 然后此时it指向的已经是下一个数据了 此时再++就跳过这个数据了 第二种情况就是这样

如果最后一个是偶数的话 把最后一个删掉之后此时it指向的就是finish了 但是还会++就跳过finish了之后再走也永远不会等于finish 第一种错误就是这样

解决

因为删除之后再里面就会自动移位了 所以如果删除的话不需要再移动it  为了分清也可以让erase返回pos位置的然后每次更新一下it 这样我们会更容易控制代码

resize

第二个参数之前string那里我们只需要一个字符  但这里是模版类的形式  第二个参数可能是内置类型的int double char都有可能 或者是自定义类型的 所以这里固定缺省值为某一个类型是不行的

这里缺省值用到了匿名对象的方式 对于自定义类型 这种方式就会调用他们的默认构造函数  但是这样子如果是内置类型怎么办呢  内置类型有构造吗 

本来内置类型是没有的 但是为了支持类似这样的写法 就让内置类型也支持构造了  如下

  

而对于里面的处理  

分为三种情况 n<size   size<n<capacity    n>capacity

对于第一种情况 直接改变_finish指向的位置就可以了

对于第二三中情况 不管哪种都直接reserve反正对于第三种情况也不会缩容   然后空间够了之后直接用迭代器finish位置到start+n之前都用val来填充就额可以了 在结束的时候finish的位置也刚好是它正确的位置

拷贝构造还是需要我们自己来写 不然默认的是浅拷贝 v2和v1指向的是同一块的空间  在我们写了析构函数之后 对于同一块空间就会析构两次就会崩掉  所以拷贝构造函数需要我们自己来写实现深拷贝

拷贝构造有以下两种方式来实现 第一种和之前的类似 先开空间再更新对应的成员变量 然后拷贝数据  第二种方式是用范围for的方式 范围for每一次从v1取一个值然后尾插给新的要创建的

这里要注意 如果我们自己写了拷贝构造  编译器默认的构造就不能使用了 我们需要自己来写一个默认构造函数(无参的或者全缺省的)     或者使用强制调用编译器默认构造的方式

对于以下自己实现的无参构造函数    

这里的具体过程是   在声明位置我们把三个成员变量都=nullptr的方式了  在调用函数里面内容之前会先用初始化列表把三个成员变量都初始化为空指针 然后才会进入函数里面 如果大于0执行下面的逻辑 如果等于0再经过初始化列表全部初始化为空指针就结束了不会再进行下面操作

赋值

下面这种方式和构造类似  不过之前可能有数据  先用clear把原来的数据给清理掉

还用另一种的方式 如下图  

直接把传的形参和现在的进行交换  这里要注意两个点

  一个是形参不能为引用了(这里要交换 不能把原来的实参给改变了) 

另一个是这个swap我们最好自己来实现一下 std库里的swap为了让各种类型都可以用 用模版的方式 在里面的实现用的是简单粗暴的 创建新变量c存a 然后a=b b=c这种方式

对于内置类型没有什么大的消耗 但是对于自定义类型也这样的话 里面有很多的数据 需要进行三次拷贝构造 代价就很大了  所以我们最好自己写一个swap的函数  如下图二我们自己写的 只是把两个对象的三个指针进行了交换 

在类模版里面也可以写一个函数模版 它既是类模版的成员函数也是函数模版  如下面的函数模版写在类模版中  这样就可以支持各种类型的迭代器来初始化vector对象

如下图v1用vector类型的v的迭代器进行初始化   v2用list类型的l1进行初始化  

但是用其他类型迭代器初始化的时候 如这里的v2   l1里面需要存的也是int类型或者是可以转换为int类型 这样才可以进行初始化

像这样在类模版里面再写一个支持各种类型的迭代器使用的函数模版   如果我们想把list对象排序 在list中排序不方便我们就可以放到vector中这样的函数模版 来排序 

写了这个之后 我们使用之前写的两个参数的构造函数初始化的时候又会报错

为什么呢

如下图 两个都是构造函数 如果是用左边的 两个形参类型可以直接都实例化为int 而对于右边第一个参数需要从int到size_t类型的隐式转换 所以会调用左边的 而对于int类型在左边进行解引用操作就会报错了

解决方法

1.在传值的时候指定类型 如这里在10后面加一个u  代表该整数是unsigned类型 就会匹配第二个构造函数了 

2.再写一个构造函数第一个参数为需要的类型 就会优先匹配这个构造函数了  实际上的std库中vector实现就是用到这种方法 写了多个这样的构造函数

.

最后reserve其实还存在一个问题

如上图 如果vector里面存的是自定义类型  在push_back里面空间不够会调用reserve reserve里面扩容是通过开新空间拷贝数据释放原来空间的方式 这里拷贝用到的是mercpy 是按字节拷贝的  对于内置类型不会出什么问题

但是如果是自定义类型的话 只能进行浅拷贝 把string里面的指针给拷贝了过去  然后释放原来空间时候会先把原来空间中指向的字符串空间先给全部释放了

如下图  原来vector存着四个string类型 每个string里面有一个指针指向堆区动态申请的空间  在开新空间之后用memcpy的方式只会把每一个string对象的指针和size cpapcity给拷贝过去  然后对于原来的空间会先把string对象str指向的空间给释放掉再释放掉旧的空间 此时新空间中存的指针指向的空间是已经被释放掉的空间 

解决

不用memcpy的方式  而是对vector中每一个数据直接赋值的方式  对于自定义类型如string 就会调用string的赋值运算符 会自动开一块新的空间 然后把内容拷贝过去

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

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

相关文章

OpenStack Neutron中的L2 Agent与L3 Agent:新手友好指南

引言&#xff1a;云网络的幕后英雄 在当今的云计算世界中&#xff0c;OpenStack作为开源云平台的佼佼者&#xff0c;为成千上万的企业提供了灵活、可扩展的基础设施服务。而在OpenStack的众多组件中&#xff0c;Neutron&#xff08;网络服务&#xff09;扮演着至关重要的角色—…

【自用】JavaSE--特殊文件Properties与XML、日志技术

特殊文件概述使用特殊文件可以存储多个有关系的数据&#xff0c;作为系统的配置信息属性文件类似于键值对&#xff0c;一一对应存储数据(比如用户名与密码)XML文件存储多个用户的多个属性更适合&#xff0c;适合存储更复杂的数据Properties注&#xff1a;这个属性文件的后缀即使…

中本聪思想与Web3的困境:从理论到现实的跨越

一、中本聪思想的核心精髓中本聪通过比特币白皮书提出的核心思想&#xff0c;可归纳为三大支柱&#xff1a;去中心化货币体系目标&#xff1a;摆脱中央机构控制&#xff0c;避免通货膨胀和政治干预&#xff08;如2008年金融危机暴露的中心化风险&#xff09;。实现路径&#xf…

Centos 用户管理

一.创建用户 在 root账户 或 sudo 权限下 1. 创建用户 useradd xiaoyangzi2.为该用户设置密码或修改密码 passwd xiaoyangzi3. 将用户加入wheel用户组 在 CentOS 中&#xff0c;属于 wheel 组的用户默认可以使用 sudo 权限。 查看所属用户组: groups xiaoyangzi将 xiaoyangzi 加…

C++枚举算法习题

1. 3的倍数枚举&#xff08;基础&#xff09;题目&#xff1a;在之间有10和50多少个数是3的倍数&#xff1f;列举这些数。 解析&#xff1a;枚举10到50之间的数&#xff0c;判断是否能被3整除。优化&#xff1a;计算第一个≥10的3的倍数&#xff08;1234&#xff09;&#xff0…

【SpringBoot系列-01】Spring Boot 启动原理深度解析

【SpringBoot系列-01】Spring Boot 启动原理深度解析 大家好&#xff01;今天咱们来好好聊聊Spring Boot的启动原理。估计不少人跟我一样&#xff0c;刚开始用Spring Boot的时候觉得这玩意儿真神奇&#xff0c;一个main方法跑起来就啥都有了。但时间长了总会好奇&#xff1a;这…

windows环境下使用vscode以及相关插件搭建c/c++的编译,调试环境

windows下使用vscode搭建c/c的编译、运行、调试环境&#xff0c;需要注意的是生成的是xxx.exe可执行文件。另外使用的编译器是mingw&#xff0c;也就是windows环境下的GNU。 我参考的网址是&#xff1a;https://zhuanlan.zhihu.com/p/1936443912806962622 文章分为2种环境搭建…

标准瓦片层级0~20,在EPSG:4326坐标系下,每个像素点代表的度数

在 EPSG:4326&#xff08;WGS84经纬度坐标系&#xff09; 下&#xff0c;瓦片层级&#xff08;Zoom Level&#xff09;的分辨率以 度/像素 为单位&#xff0c;其计算遵循 TMS Global Geodetic 规范&#xff08;单位&#xff1a;度&#xff09;。以下是 标准层级 0 至 20 的分辨…

Unity高级剔除技术全解析

目录 ​编辑层级剔除&#xff08;Layer Culling&#xff09;原理详解 代码示例 业务应用场景 距离剔除&#xff08;Distance Culling&#xff09;技术细节 进阶实现 开放世界优化技巧 视口裁剪&#xff08;Viewport Culling&#xff09;多摄像机协作方案 高级应用场景 …

[Linux] Linux文件系统基本管理

目录 识别文件系统和设备 Linux 中设备 Linux 文件系统 查看设备和文件系统 lsblk命令 df命令 du命令 案例&#xff1a;查看根文件系统中哪个文件占用了最大空间 环境准备 查找过程 挂载和卸载文件系统 环境准备 挂载文件系统 卸载文件系统 卸载失败处理 lsof …

如何在 Ubuntu 24.04 Server 或 Desktop 上安装 XFCE

在 Ubuntu 24.04 上更改当前桌面环境或添加新的桌面环境并不是一项艰巨的任务。大多数流行的 Linux 桌面环境,包括 XFCE,都可以通过默认的 Ubuntu 24.04 LTS 系统仓库安装。在本教程中,我们将学习如何使用 Tasksel 工具在 Ubuntu Linux 上安装和配置 XFCE。 访问终端并运行…

linux下用c++11写一个UDP回显程序

需求&#xff1a;1&#xff09;从2个UDP端口接收数据&#xff0c;并在同样的端口回显。echo2&#xff09;多个处理线程&#xff0c;多个发送线程&#xff1b;3&#xff09;使用条件变量唤醒&#xff1b;#include <stack> #include <mutex> #include <atomic>…

MySQL 深分页优化与条件分页:把 OFFSET 换成“游标”,再用覆盖索引抄近路

MySQL 深分页优化与条件分页:把 OFFSET 换成“游标”,再用覆盖索引抄近路 这不是“玄学调优”,而是可复制的方案。本文用可复现的 DDL/造数脚本,演示为什么 OFFSET 越大越慢,如何用 条件游标(Keyset Pagination) 替换它,并配上 覆盖索引。还会教你看 EXPLAIN/EXPLAIN A…

Unity 绳子插件 ObjRope 使用简记

Unity 绳子插件&#xff0c;是一个基于物理的、高度逼真且可交互的绳索模拟解决方案。 其性能良好&#xff0c;能够运行在小游戏平台。 一、插件基本 插件资源商店地址&#xff1a; Obi Rope | Physics | Unity Asset Store 官方文档&#xff08;手册&#xff09;&#xff…

demo 通讯录 + 城市选择器 (字母索引左右联动 ListItemGroup+AlphabetIndexer)笔记

一、城市选择器实现笔记1. 双层 for 循环渲染数据结构interface BKCityContent {initial: string; // 字母索引cityNameList: string[]; // 城市列表 }核心实现// 外层循环&#xff1a;字母分组 - 遍历城市数据&#xff0c;按字母分组显示 ForEach(this.cityContentList, (item…

【总结型】c语言中的位运算

位运算包括 & | ^ ~ << >>按位与 将某些变量中的某些位清0同时保持其他位不变。也可以用来获取变量中的某一位。 例如&#xff1a;将int型变量n低8位全置为0&#xff0c;其余位保持不变。 n n & 0xffffff00 如何判断一个int型变量n的第七位。 n & 0x8…

如何在FastAPI中玩转APScheduler,实现动态定时任务的魔法?

url: /posts/4fb9e30bb20956319c783e21897a667a/ title: 如何在FastAPI中玩转APScheduler,实现动态定时任务的魔法? date: 2025-08-16T01:14:26+08:00 lastmod: 2025-08-16T01:14:26+08:00 author: cmdragon summary: APScheduler是Python中强大的任务调度库,支持任务持久化…

GitHub的简单使用方法----(5)

最后一篇简单讲讲git管理远程仓库 1.目的 备份&#xff0c;实现代码共享集中化管理 &#xff08;将本地仓库同步到git远程仓库中&#xff09; git clone 仓库地址 以下图为示例&#xff0c;我打开了一个别人的项目仓库&#xff0c;点击code能看到仓库地址 等待完成即可 如…

C++ STL-string类底层实现

摘要&#xff1a; 本文实现了一个简易的string类&#xff0c;主要包含以下功能&#xff1a; 1. 默认成员函数&#xff1a;构造函数&#xff08;默认/参数化&#xff09;、拷贝构造、赋值重载和析构函数&#xff0c;采用深拷贝避免内存问题&#xff1b; 2. 迭代器支持&#xff1…

【LeetCode每日一题】

每日一题3. 无重复字符的最长子串题目总体思路代码1.两数之和题目总体思路代码15. 三数之和题目总体思路代码2025.8.153. 无重复字符的最长子串 题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3…