vector的模拟实现

什么是vector

vector是一个封装了动态大小数组的顺序容器跟任意其它类型容器一样,它能够存放各种类型的对象。

模拟实现

实现前的准备

在实现vector之前,为了和库里的区分开需要将实现的vector放在一个自定义的命名空间里。而且vector需要实现成模版的方式,所以需要我们传模版参数,模版参数也就是顺序容器所存的数据类型。而且根据库里的vector,有三个关键的成员变量,分别指向顺序容器的起始位置,数据容量位置,空间容量位置。

namespace cr
{template<class T>class vector{public:private:iterator _start;iterator _finish;iterator _endofstorage;};
}

迭代器的相关实现 

typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const iterator begin() const
{return _start;
}
const iterator end() const
{return _finish;
}

这里要提醒的就是,_finish指向的是最后一个有效数据的下一个位置,_endofstorage指向的是对象的最大容量位置处的下一个位置。

下标引用operator[]

T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}

这里需要注意的是:不要忘记断言

容器大小

size_t capacity() const
{return _endofstorage - _start;
}
size_t size() const 
{return _finish - _start;
}

为了保证const对象调用该函数时也可以正常通过,所以对该函数进行const修饰。

析构函数

~vector()
{delete[] _start;
}

扩容reserve

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

这里进行扩容时一定要注意判断空间大小关系,尤其是要注意三个指针的指向问题,在此之前记录好原空间中有效数据的个数,因为执行new操作都是异地扩容,所以_start存的地址肯定发生改变,如果此时通过_start求得的_finish就一定会出错,所以需要提前记录好位置关系但是该函数不一定正确

尾插数据push_back 

void push_back(const T& x) 
{if (_finish == _endofstorage)reserve(size() == 0 ? 4 : capacity() * 2);*_finish = x;_finish++;
}

push_back需要注意的地方就是当空间大小为0时需要扩的大小,以及_finish++。这里来验证一下自内置类型的数据结果:

 再看看string类的数据结果:引发了异常: 读取访问权限冲突。

 其实这里的问题哦就是reserve的实现出了问题:

 当空间不够时会发生扩容,就会调用memcpy函数,memcpy是库里的,而且我们知道memcpy内部就是通过值拷贝将_start解引用进行拷贝给tmp(_start是一个string类的指针),所以此时拷贝好的数据相当于是浅拷贝,指向如下:

 然后调用析构函数,将_start空间内存释放,但是_start内部数据是string类,所以会先调用string的析构函数,释放string得空间,所以此时就是报错所在:tmp内的string同样也销毁了。

修改reverse函数

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){//memcpy(tmp, _start, sizeof(T) * sz);for (int i = 0; i < sz; i++){tmp[i] = _start[i];//如果T是string,调用std::string的赋值重载}delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

这种拷贝的方式就有效避免了浅拷贝的问题,如果模版参数T是string类的话,tmp[i]的类型就是string,所以此时的赋值就会调用string的赋值重载operator=()进行深拷贝,所以就不会出问题。

改变有效数据个数resize

void resize(size_t n, const T& x = T())//匿名对象
{if (n > size()){reserve(n);while (_finish != _endofstorage){*_finish = x;_finish++;}}else{_finish = _start + n;}}

这里要注意的就是T x给的缺省值T(),T()其实就是调用匿名对象的构造函数,然后在调用拷贝构造给x对象,而编译器会优化成直接的构造。匿名对象具有常性(不能&(同一块空间)不加const),出了该行就自动析构,const引用可以延长匿名对象的生命周期。但是如果是内置类型,这样写也是没问题的,其实内置类型也是可以看做有构造函数的。就如下测试:

 构造函数

vector(size_t n=0,const T& val=T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n);while (n--){push_back(val);}
}

拷贝构造

vector(const vector<T>& v):_start(new T[v.capacity()]),_finish(_start),_endofstorage(_start+v.capacity())
{for (auto ch : v){*_finish = ch;//如果T是string,调用std::string的赋值重载_finish++;}
}

通过迭代器区间初始化的构造函数

//在一个类模版里面还可以继续写模版函数
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{while (first != last){push_back(*first);first++;}
}

这里迭代器区间的构造函数是需要实现成模版类型的,因为库里面的STL说明不同的容器可以通过迭代器区间进行初始化,只不过有点不兼容的可以进行强制类型转换:

但是当执行到构造函数时:

cr::vector<int> vv(3, 1);

会报错非法的间接寻址  

原因:当我们调用构造函数时,会优先找最匹配的构造函数进行调用,所以此时以上的三种构造函数就会调用迭代器区间构造函数,而不是第一种


vector(size_t n=0,const T& val=T())//一般构造
vector(InputIterator first, InputIterator last)迭代器区间构造

调用第一种时会发生int类型到size_t类型的转换,而第三种构造函数会直接调用,因为第三种构造有模版参数,而对于两个参数(3,1)都是int类型,所以此时模版形参InputIterator就是替换成int型,所以此时第三种更匹配。

解决方法:

  1. 将第一个构造函数的形参改变成int类型
  2. 再生成一个构造函数形成重载
    vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
    {reserve(n);while (n--){push_back(val);}
    }

赋值重载operator= 

vector<T>& operator=(vector<T> v)
{swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);return *this;
}

这里是要一个返回值的,主要是避免连等的情况:a=b=c;

在某位置插入数据insert

void insert(iterator pos, T x)
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage);{reserve(_start==nullptr?4:capacity() * 2);}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

以上代码其实是有问题的,如下测试

调试时你会发现是在insert内部出了问题 而且会在下面代码不断循环

while (end >= pos)
{*(end + 1) = *end;end--;
}

所以问题极可能出现在上面的代码段: 

此时pos依旧是nullptr,就相当于pos指向的依旧是原空间的地址,插入数据扩容时,空间都发生了变化,所以再拿原空间的指针和新空间的指针比较就不妥

修改代码:其实就是记录pos位置和_start之间的差距,好在扩容到新空间时,将差距补上

void insert(iterator pos, T x)//insert使用之后迭代器失效最好不要使用
{assert(pos >= _start && pos <= _finish);//不要忘记断言if (_finish == _endofstorage);{size_t len = pos - _start;reserve(_start==nullptr?4:capacity() * 2);//异地扩容改变了_start,可能导致pos成为野指针pos = _start + len;}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

 虽然修改了代码,编译起来也没问题,但是如果你执行下列操作还是跑不通:

 此时是断言处出问题了,其实这种情况属于迭代器失效因为你插入数据时发生了扩容了,所以同上面一样,it指针指向的是原空间的首个位置,所以就会错误

解决方法:每次插入数据是重新调用迭代器v.begin()或v.end()来获得迭代器的位置。

在某位置删除数据erase

void erase(iterator pos)
{assert(pos < _finish&& pos >= _start);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;end++;}_finish--;
}

这里和inssert是不同的,erase不会发生扩容,所以也不存在迭代器失效,但是这erase要注意的就是,每次删除一个数据时位置会发生挪动,所以一般在刷题时一定要注意这个小细节。

而VS下的erase是并不是这样实现的,VS下的erase也只能使用一次,用完了该指针也就是失效了。尽管进行it++还是--来改变it再使用也是不可以的。

 所以可以借鉴insert的用法:

 但是VS下的erase其实是有返回值的,

其实返回的就是你删除的那个数据的下一个位置,但是位置会发生挪动,所以,返回的也就是pos位置 


如有解释不当,欢迎大家留言!

 

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

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

相关文章

ITIL4—度量和报告实践

1. 关于本文 本文为度量和报告实践提供了实用指南&#xff0c;分为五个主要部分&#xff0c;涵盖&#xff1a; 本实践的基本信息本实践相关的流程和活动&#xff0c;及其在服务价值链中的作用参与本实践的组织和人员支持本实践的信息和技术合作伙伴和供应商在本实践中的注意事…

P1123 取数游戏

取数游戏 题目描述 一个 N M N\times M NM 的由非负整数构成的数字矩阵&#xff0c;你需要在其中取出若干个数字&#xff0c;使得取出的任意两个数字不相邻&#xff08;若一个数字在另外一个数字相邻 8 8 8 个格子中的一个即认为这两个数字相邻&#xff09;&#xff0c;求…

React源码解析18(1)------ React.createElement 和 jsx

1.React.createElement 我们知道在React17版本之前&#xff0c;我们在项目中是一定需要引入react的。 import React from “react” 即便我们有时候没有使用到React&#xff0c;也需要引入。原因是什么呢&#xff1f; 在React项目中&#xff0c;如果我们使用了模板语法JSX&am…

性能测试—Jmeter工具

文章目录 性能测试1. 术语介绍2. 方法3. 应用场景4. 工具&#xff08;Jmeter&#xff09;4.1 介绍4.2 元件和组件4.2.2 元件4.2.1 组件 4.3 作用域4.4 参数化4.5 执行脚本 性能测试 1. 术语介绍 响应时间(Response time)&#xff1a;对请求作出响应所需要的时间。 在互联网上对…

小型双轮差速底盘机器人实现红外跟随功能

1. 功能说明 本文示例将实现R023样机小型双轮差速底盘跟随人移动的功能。在小型双轮差速底盘前方按下图所示安装3个 近红外传感器&#xff0c;制作一个红外线发射源&#xff0c;实现当红外发射源在机器人的检测范围内任意放置或移动时&#xff0c;机器人能追踪该发射源。 2. 电…

数学建模学习(10):遗传算法

遗传算法简介 • 遗传算法&#xff08;Genetic Algorithms&#xff09;是基于生物进化理论的原理发展起来的一种广为 应用的、高效的随机搜索与优化的方法。其主要特点是群体搜索策略和群体中个体之 间的信息交换&#xff0c;搜索不依赖于梯度信息。它是20世纪70年代初期由美国…

数据结构--栈和队列

文章目录 栈的概念和结构栈的实现栈的数据结构栈的初始化和销毁出栈和入栈获取栈顶、大小&#xff0c;判空 队列的概念和结构队列的实现队列的数据结构队列的初始化和销毁队列的插入 队列的删除获取队头和队尾的数据获取队列长度和判空 栈和队列的一些题目1.有效的括号2.用队列…

【Leetcode】155. 最小栈、JZ31 栈的压入、弹出序列

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 155. 最小栈 155. 最小栈 题目描述; 设计一个支持 push &#xff0c;pop &#xff0c;top …

【配置环境】Linux下安装MySQL

目录 一&#xff0c;环境 二&#xff0c;安装步骤 1.使用包管理器安装MySQL 2.配置MySQL的安全选项 3.设置root用户使用密码进行身份验证&#xff08;可选&#xff09; 三&#xff0c;拓展知识 1.如何修改MySQL的密码策略&#xff1f; 2.实现连接MySQL数据库的测试代码…

docker基础

安装docker 参考安装&#xff1a; https://docs.docker.com/engine/install/centos/#installation-methods 开机启动 systemctl enable docker.service systemctl is-enabled docker.service 安装docker compose https://github.com/docker/compose/releases/tag/v2.17.2 …

Idea报错:Cannot resolve symbol “springframework“以及各种依赖包

问题描述&#xff1a; Idea导入了maven项目之后出现报错Cannot resolve symbol “springframework” &#xff0c;识别不了这个标识或者找不到这个包&#xff0c;明明这些依赖和包都有就是出现报错&#xff0c;并且运行按钮变成灰色 解决办法&#xff1a; 其实这个原因大概率就…

【rust/egui】(二)看看template的main函数:日志输出以及eframe run_native

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 开始 首先让我们看看main.rs中有些什么…

【Linux系统编程】21.echo、env、fork、getpid、getppid

目录 echo PATH SHELL TERM LANG HOME env fork 返回值 getpid getppid 测试代码1 测试结果 测试代码2 测试结果 父子进程相同 父子进程不同 父子进程共享 echo 查看单个环境变量。 PATH 可执行文件的搜索路径。 SHELL 当前Shell。 TERM 当前终端类型。终端…

山西电力市场日前价格预测【2023-08-14】

日前价格预测 预测明日&#xff08;2023-08-14&#xff09;山西电力市场全天平均日前电价为322.03元/MWh。其中&#xff0c;最高日前电价为366.98元/MWh&#xff0c;预计出现在19: 30。最低日前电价为286.57元/MWh&#xff0c;预计出现在13: 15。 价差方向预测 1&#xff1a; 实…

【ARM Cache 系列文章 9 番外篇 -- ARMv9 系列 Core 介绍】

文章目录 ARMv9 系列CoreARM Cortex-A510 介绍ARM Cortex-A715ARM Cortex-A720 ARMv9 系列Core 2021年5月Arm公布了其最新3款CPU和3款GPU核心设计&#xff0c;三款新CPU分别是旗舰核心Cortex-X2、高性能核心Cortex-A710、高能效核心Cortex-A510 CPU&#xff0c;三款新GPU核心则…

《面试1v1》ElasticSearch 集群索引分片

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

【学会动态规划】买卖股票的最佳时机 IV(18)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

STM32F103C8T6开发笔记1:有线陀螺仪二自由度机械臂

经过之前几天的快速学习&#xff0c;今日尝试组装一款基于MPU6050陀螺仪控制的二自由度机械臂&#xff0c;本文对其使用器材以及基本原理进行介绍~ 组装效果图&#xff1a; 主要元器件如下&#xff1a; 器件个数15 KG以上 舵机3适合舵机的金属夹爪118650电池电源12V1云台支架2…

WebRTC音视频通话-实现GPUImage视频美颜滤镜效果iOS

WebRTC音视频通话-实现GPUImage视频美颜滤镜效果 在WebRTC音视频通话的GPUImage美颜效果图如下 可以看下 之前搭建ossrs服务&#xff0c;可以查看&#xff1a;https://blog.csdn.net/gloryFlow/article/details/132257196 之前实现iOS端调用ossrs音视频通话&#xff0c;可以查…

SCAU操作系统知识点之(一)计算机系统概述

缩写词&#xff1a; OS: Operating System 操作系统 PSW: Program Status Word 程序状态字 FCFS: First Come First Serve 先来先服务 PCB: Process Control Block 进程控制块 DMA: Direct Memory Access 直接存储器存取 MMU: Memory Management Unit 内存管理单元 SSTF: Short…