Cpp学习——vector模拟实现

 

vector简介

在模拟实现vector之前,首先就得知道vector是个啥?vector是个啥呢?vector是一个stl里面的容器,并且是一个模板容器。它就像是一个顺序表模板。还记得顺序表吧?之前我实现的顺序表只能弄整形的数据,但是vector却可以弄几乎所有的数据。所以要实现vector便可以参考实现顺序表的功能来实现,并且还可以结合一下stl里面的源代码。

 vector实现

1.vector的成员

在这里就得来参考一下stl里面的源代码了。看一看stl里面的vector成员是什么。源代码:

这便是vs2019下面的vector的成员,pointer便是typedef后的指针。但是这个指针可不是一般的指针,而是模板指针。来看一下:

 vs2019下面的源代码复杂了,复杂到让我们这些小白看不懂。反正就记住vector的成员是下面三个便可以了:_start,_finish,_endofstorage。分别代表的意思就是:指向vector容器的开头,容器内内容的结尾,容器容量的结尾。那我们在实现时该怎么定义呢?我们可以像下面一样去定义:

namespace area{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endofstrage;
};
}

1.首先搞一块area域来写自己的vector。

2.建立模板,并将模板的指针类型T* typedef为iterator,const T*定义为const_iterator。要在public的区域内。

3.再用typedef后的模板指针来定义成员。

2.vector初始化构造以及capacity(),size()函数

要使用一个对象,就得初始化一个对象。初始化对象应该用什么值呢?很明显,指针类型就得用空指针来初始化。初始化函数如下:

    vector() :_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}

接下来便是写一个尾插函数,但是这个函数是有问题的。尾插函数如下:

void push_back( const T& x){int sz = size();if (_finish == _endofstorage){int cp = capacity() == 0 ? 4 : 2 * capacity();T* tmp = new T[cp];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[]_start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + cp;}*_finish = x;++_finish;}

3.reserve函数与resize函数

resever函数实现的是一个扩容的操作,resize函数实现的就是一个扩容加初始化的函数了。所以按照两者的功能便可以先实现reserve函数再实现resize函数。reserve函数实现代码如下:

void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}

有了reserve()函数接下来实现resize()函数就简单了。代码如下:

void resize(size_t n,const T& val = T()){reserve(n);//先检查是否扩容或者是否要开空间。iterator it = end();//将数据尾到容量尾这一段空间的元素初始化掉while (it != _endofstorage){*it = val;it++;}_finish += n;}

这里值的考究的便是在数据的结尾到容器的结尾到底该放什么数据呢?这里给的缺省值是

T()。这是一个匿名构造,构造出来的值是根据实例化出来的T的默认构造的值。这样便可以避免因为给死一个缺省值而导致的类型不匹配的错误。有了reserve()函数以后便可以对push_back()函数进行改良,改良如下:

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

4,insert()函数与erase()函数

这两个函数实现的便是vector的中间插入与删除的功能。但是在这两个函数实现的过程中会出现迭代器失效的问题。

1.先来实现一下insert()函数,代码如下:

void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);reserve(size());//检查扩容iterator end = _finish;while (end != pos){*end = *(end - 1);end--;}*pos = val;_finish++;}

但是这个代码是有一丝丝问题的。比如当我插入以下数据时:

	void test_vector3(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//尾插五个元素for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 22);//再中间插入三个元素v1.insert(v1.begin()+2, 66);v1.insert(v1.end(), 99);for (auto e : v1){cout << e << " ";}cout << endl;}

结果:

这样是可以正常的跑起来的。但是当我的数据变成:

void test_vector3(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 22);v1.insert(v1.begin()+2, 66);v1.insert(v1.end(), 99);v1.insert(v1.end(), 100);for (auto e : v1){cout << e << " ";}cout << endl;}

 也就是再中间插入一个100时便会发生错误。错误如下:

为什么呢?其实这里是发生了迭代器失效的问题,从而引发了野指针的问题。为什么会发生迭代器失效呢?原因是发生了扩容。原来我的数据是这样的:

 这个时候我就得发生扩容了吧,因为容量已经满了。我们的扩容是怎么扩的呢?是这样扩的:

T* tmp = new T[n];if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;}_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;

原来的数据经过扩容操作以后就会变成:

 但是我的pos是在那个位置呢?是在原来那个没有扩容的小空间上。为什么?因为我是在用begin()迭代器来完成插入操作。在调用begin()的时候,还没有扩容。所以pos的位置便在原来的那个已被销毁的空间上,所以pos变成了野指针。pos与_finish的位置关系如下图:

但是我的结束条件是这个:end!=pos,end一开始便是_finish。

iterator end = _finish;
while (end != pos)
{*end = *(end - 1);end--;}

 所以end与pos是在两块不同的空间上的。说以这个条件是不会结束的。所以end会一直访问一些不该访问的空间。所以发生了错误。那我们该如何去修改呢?其实很简单,在扩容之前记录一下pos与_start的偏移量,在扩容后将pos的位置更新一下便可以了。

void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);int len = pos - _start;//记录偏移量reserve(size()+1);//插入一个数据后容量加1pos = _start + len;//更行positerator end = _finish;while (end != pos){*end = *(end - 1);end--;}*pos = val;_finish++;}

然后便可以正常运行了:

 2.erase()函数,实现代码如下:

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

在正常情况下:

void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e: v1){cout << e << " ";}cout << endl;v1.erase(v1.begin());v1.erase(v1.end());for (auto e : v1){cout << e << " ";}cout << endl;}

这是可以正常使用的:

但是在某些特殊的场景之下这就会发生迭代器失效的情况。比如我要将一列数据中的偶数给删除时。在以下情况下,用我写的erase代码的情况如下:

1。偶数不在尾部且偶数不连续:

void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>::	iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;}

 运行结果是正常的:

 2.偶数有连续的情况:

void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(2);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>::	iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;}

运行结果是有错误的:结果不对

 3.当尾部的数据是偶数时:程序崩溃

void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);v1.push_back(8);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>::	iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}it++;}for (auto e : v1){cout << e << " ";}cout << endl;}
}

结果:

这是为什么呢?当数据内有连续偶数时删不干净其实就是我们的操作逻辑写错了。当要删除元素是我们不应该++it而是应该让it保持不动,对移动后的数据还得来一次判断。所以我们要将删除逻辑改为这样:

void test_vector5(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(2);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);for (auto e: v1){cout << e << " ";}cout << endl;vector<int>::	iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);//删除后不应该++,应改再对这个位置上的新数据进行判断}else{it++//不是偶数时再++}}for (auto e : v1){cout << e << " ";}cout << endl;}

结果:

对于尾巴上是偶数的删除,结果也是对的:

这里的迭代器失效问题就是迭代器位置失去意义。

 5.深拷贝与浅拷贝问题(大坑)

先来写一个拷贝构造函数:

vector( vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){T* tmp = new T[v.capacity()];if (v._start){memcpy(tmp, v._start, sizeof(T) * v.size());}_start = tmp;_finish = tmp + v.size();_endofstorage = tmp + v.capacity();}

来个整型试试看:

void test_vector6(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int>v2(v1);for (auto e : v2){cout << e << " ";}cout << endl;}

结果:

程序正常运行。

 再来一个string:

void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");//v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;}

只插入四个的话,程序正常运行:

但是,当我要插入第五个元素时:

void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;}

程序崩掉了:

为啥呢?其实还是因为扩容,也就是reserve()函数。再来看看reserve()函数的实现:

void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}

 这里拷贝数据的方式是memcpy()。拷贝完了以后就要将_start里的数据给释放掉。但是memcpy函数实现的是浅拷贝,而string对象里面可是有指针的。这样的话,在释放掉原来的空间后再调用析构函数就会对同一块空间进行两次释放导致报错。该怎么改呢?调用深拷贝就行了。改进如下:

void reserve(size_t n){int sz = size();if (capacity() <= n){T* tmp = new T[n];if (_start){for (int i = 0;i < size();i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}

为什么这样就可以了呢?这是因为自定义类型的=实现的就是深拷贝。

再来调用vector的拷贝构造:

void test_vector7(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout<<endl;        vector<string>v2(v1);//拷贝构造for (auto e : v2){cout << e << " ";}cout << endl;}

运行:出错

其实这里的原因还是因为memcpy()浅拷贝导致的对同一块空间析构两次导致的错误。所以这里要将拷贝构造函数进行改造,改造如下:

vector( vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto e : v){push_back(e);}}

然后我们的程序便可以跑起来了。

结果:

 

6.其它的构造函数

1.迭代器区间构造函数。

作用:通过某个类型的容器的迭代器来初始化。代码如下:

template<class InputIterator>//在类里面定义一个模板,这样就可以让这个迭代器区间构造的函数更加方便使用
vector(InputIterator first, InputIterator end){while (first != end){push_back(*first);first++;}}

现在尝试通过下面的代码来进行测试:

void test_vector8(){vector<string>v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");for (auto e : v1){cout << e << " ";}cout << endl;vector<string>v2(v1.begin(), v1.end());for (auto e : v2){cout << e << " ";}cout << endl;}

结果:正常

 2.指定大小和初始化值的函数

vector(int n,  const T& val = T()){reserve(n);for (int i = 0;i < n;i++){push_back(val);}}

这里需要注意一点,n的类型得是int才行。要不然在调用这个函数来构造一个vector<int>类型的对象时就会发生类型匹配上的错误。

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

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

相关文章

微信小程序 map地图(轨迹)

allMarkers效果图 废话少说直接上马&#xff08;最后是我遇到的问题&#xff09; cover-view是气泡弹窗&#xff0c;可以自定义弹窗&#xff0c;要配合js&#xff1a;customCallout&#xff0c;如果是非自定义的话&#xff1a;callout&#xff08;可以修改颜色、边框宽度、圆角…

Ceph Reef版本 RBD 性能测试:80万写IOPS(10节点、60个NVMe SSD)

2023-05-16 08:30 发表于上海 摘自&#xff1a;https://mp.weixin.qq.com/s/mKkPElmCktoZaRk0m0IbqA 1、背景 Ceph 社区最近冻结了即将发布的 Ceph Reef 版本&#xff0c;今天我们研究一下 Ceph Reef 版本在 10 个节点、60 个 NVMe 磁盘的集群上的 RBD 性能。 在确保硬件没有…

医疗保健中的 NLP:实体链接

一、说明 HEalthcare和生命科学行业产生大量数据&#xff0c;这些数据是由合规性和监管要求&#xff0c;记录保存&#xff0c;研究论文等驱动的。但随着数据量的增加&#xff0c;搜索用于研究目的的必要文件和文章以及数据结构成为一个更加复杂和耗时的过程。例如&#xff0c;如…

小米平板6Max14即将发布:自研G1 电池管理芯片,支持33W反向快充

明天晚上7点&#xff08;8 月 14 日&#xff09;&#xff0c;雷军将进行年度演讲&#xff0c;重点探讨“成长”主题。与此同时&#xff0c;小米将推出一系列全新产品&#xff0c;其中包括备受瞩目的小米MIX Fold 3折叠屏手机和小米平板6 Max 14。近期&#xff0c;小米官方一直在…

MiniPaint:在线图像编辑利器【在线PS】

MiniPaint在线图像编辑器使用 HTML5 实现图像的在线创建与编辑&#xff0c;在线PS&#xff0c;支持超过40种效果滤镜&#xff0c;无需本地安装&#xff0c;在很多应用场景中可以替代PhotopShop等传统软件。 访问地址&#xff1a;MiniPaint - 在线PS - 在线图像编辑。 1、打开图…

词法分析器的设计与实现

1、实验目的及要求 1.1、实验目的 加深对词法分析器的工作过程的理解&#xff1b;加强对词法分析方法的掌握&#xff1b;能够采用一种编程语言实现简单的词法分析程序&#xff1b;能够使用自己编写的分析程序对简单的程序段进行词法分析。 1.2、实验要求 1&#xff09;对单词…

Towards Open World Object Detection【论文解析】

Towards Open World Object Detection 摘要1 介绍2 相关研究3 开放世界目标检测4 ORE:开放世界目标检测器4.1 对比聚类4.2 RPN自动标注未知类别4.3 基于能量的未知标识4.4 减少遗忘 5 实验5.1开放世界评估协议5.2 实现细节5.3 开放世界目标检测结果5.4 增量目标检测结果 6 讨论…

Postman

Postman 简介下载安装 简介 Postman 是一款用于测试和开发 API&#xff08;应用程序编程接口&#xff09;的工具&#xff0c;它提供了用户友好的界面和丰富的功能&#xff0c;帮助开发者轻松地创建、测试、调试和文档化各种类型的 API。无论是在构建 Web 应用、移动应用还是其…

2023.08.13 学习周报

文章目录 摘要文献阅读1.题目2.要点3.问题4.解决方案5.本文贡献6.方法6.1 特征选择6.2 时间序列平稳性检测与数据分解6.3 基于GRU神经网络的PM2.5浓度预测 7.实验7.1 网络参数7.2 实验结果7.3 对比实验 8.讨论9.结论10.展望 PINNS模型1.自动微分2.全连接神经网络3.PINNs模型的P…

优雅地处理RabbitMQ中的消息丢失

目录 一、异常处理 二、消息重试机制 三、错误日志记录 四、死信队列 五、监控与告警 优雅地处理RabbitMQ中的消息丢失对于构建可靠的消息系统至关重要。下面将介绍一些优雅处理消息丢失的方案&#xff0c;包括异常处理、重试机制、错误日志记录、死信队列和监控告警等。…

Mac 卸载appium

安装了最新版的appium 2.0.1,使用中各种问题&#xff0c;卡顿....,最终决定回退的。记录下卸载的过程 1.打开终端应用程序 2.卸载全局安装的 Appium 运行以下命令以卸载全局安装的 Appium&#xff1a; npm uninstall -g appium 出现报错&#xff1a;Error: EACCES: permiss…

vue3项目中引入dialog插件,支持最大最小化、还原、拖拽

效果图&#xff1a; 上图是layui-vue组件库中的layer插件&#xff0c;我的项目使用的是element-plus组件库&#xff0c;在用不上layui组件库的情况下&#xff0c;就单独引入layui/layer-vue这个弹层插件就可以了 npm地址&#xff1a;layui/layer-vue - npm layui-vue组件库地址…

推荐几款流行的项目管理系统,助力高效团队协作!

项目式管理是目前非常流行的企业管理方法&#xff0c;这种方法让是如何在确保时间、技术、经费和性能指标的条件下&#xff0c;以尽可能高的效率完成预定目标&#xff0c;让所有与企业相关方满意。在这种模式下&#xff0c;团队的层次关系不再那么重要&#xff0c;大家以项目结…

【对于一维信号的匹配】对一个一维(时间)信号y使用自定义基B执行匹配追踪(MP)研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Android Https

本质&#xff1a;在客户端和服务端使用非对称加密协商出一套对称密钥&#xff0c;每次发送数据前加密&#xff0c;收到后解密&#xff0c;达到加密传输 http ssl 在http之下增加了安全层&#xff0c;用于保障http的加密传输 HTTPS连接 TLS连接步骤 1.客户端发送 client h…

Profibus DP主站转Modbus TCP网关profibus主站模拟软件

捷米JM-DPM-TCP网关。这款产品在Profibus总线侧实现了主站功能&#xff0c;在以太网侧实现了ModbusTcp服务器功能&#xff0c;为我们的工业自动化网络带来了全新的可能。 捷米JM-DPM-TCP网关是如何实现这些功能的呢&#xff1f;首先&#xff0c;让我们来看看它的Profibus总线侧…

HCIP学习--BGP3

目录 前置内容 BGP下一跳的修改问题 BGP的属性 配置 PrefVal权重属性 负载分担 LocPrf 负载分担 NextHop AS-PATH Ogn 配置 MED 配置 BGP选路规则 BGP的社团属性 配置及解释 前置内容 HCIP学习--BGP1_板栗妖怪的博客-CSDN博客 HCIP学习--BGP2_板栗妖怪的博客…

Linux 终端命令之文件浏览(4) head, tail

Linux 文件浏览命令 cat, more, less, head, tail&#xff0c;此五个文件浏览类的命令皆为外部命令。 hannHannYang:~$ which cat /usr/bin/cat hannHannYang:~$ which more /usr/bin/more hannHannYang:~$ which less /usr/bin/less hannHannYang:~$ which head /usr/bin/he…

Baumer工业相机堡盟工业相机如何通过BGAPISDK设置相机的固定帧率(C#)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机的固定帧率功能的技术背景CameraExplorer如何查看相机固定帧率功能在BGAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过BGAPI SDK设置相机固定帧…

剑指 Offer 32 - II. 从上到下打印二叉树 II

题目描述 从上到下按层打印二叉树&#xff0c;同一层的节点按从左到右的顺序打印&#xff0c;每一层打印到一行。 示例 思路 采用队列存储二叉树&#xff0c;利用BFS算法对树进行从上到下的层次遍历 如何存储每一层的元素&#xff1f;——利用for循环把当前队列的元素存储进…