C++STL的vector模拟实现

文章目录

  • 前言
  • 成员变量
  • 成员函数
    • 构造函数
    • push_back
    • pop_back
    • insert
    • erase
    • 析构函数
    • 拷贝构造

前言

成员变量

namespace but
{template<class T>class vector{public:typedef T* iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;};
}

我们之前实现顺序表是用指向数组的的指针和数组个数和容量来维护顺序表的,这里用三个指针来实现其实大差不差。
在这里插入图片描述

成员函数

构造函数

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

push_back

void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);//避免容量为0扩容还是0的情况}*_finish = x;++_finish;
}

首先放数据怎么放呢?
直接赋值。

为什么可以直接赋值?
因为空间是new出来的,如果是内置类型,有没有初始化都可以直接赋值。
但是自定义类型没有初始化不能直接赋值。

扩容

void reserve(size_t n)
{//避免缩容//1.缩容的代价太大//2.反复缩容与扩容,降低了性能。if (n > capacity()){size_t sz = size();T* tmp = new T[n];//如果旧空间没有数据就不用拷贝了if (_start){//因为是类型不一定是字符串,所以得使用memcpymemcpy(tmp, _start, sizeof(T)*size());delete[] _start;}_start = tmp;//这里有个小坑//_finish=_start + size();//提前把size()记录下来,防止这里出错//改成tmp也可以,但是有点影响我们原本的理解,不利于维护。_finish = _start + sz;_end_of_storage = _start + n;}
}

我们把其他一些需要用到的代码补上,然后测试一下。

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

迭代器

//这个普通迭代器实现起来也相当简单
iterator begin()
{return _start;
}iterator end()
{return _finish;
}

pop_back

删除数据好像也很简单,直接finish- -就可以,但是也有一些问题,那具体有什么问题呢?我们来看一下。
在这里插入图片描述
我们简单测试一下。
我们一直pop,就出问题了。
在这里插入图片描述
_finish一直减就走到前面去了,这样我们使用迭代器就出问题了。

我们简单改一下。

void pop_back()
{assert(!empty());--_finish;
}
bool empty()
{return _start == _finish;
}

针对const对象的访问
在这里插入图片描述
本质还是涉及到权限的放大,我们把成员函数都改为const就可以了。

resize()

void resize(size_t n, T val = T())//T()默认构造,是匿名对象,具体解释看下面
{if (n < size()){// 删除数据_finish = _start + n;}else{if (n > capacity())reserve(n);while (_finish != _start+n){*_finish = val;++_finish;}}
}

上面resize的缺省值能不能给0?
其实答案很明显, 显然不行,因为T是泛型编程,类型不一定是int,如果是double或者指针,对象都不行。

那问题又来了,int有默认构造函数吗?
我们在之前学习类和对象的时候知道,内置类型是没有构造函数的,但是有了模板之后它需要有。

在这里插入图片描述

insert

在这里插入图片描述
下面这段代码有什么问题?
在这里插入图片描述
如果容量不够,挪动数据就越界了。
在这里插入图片描述
除了容量不够还有没有其他什么问题?
提示一下pos==0; 好吧,其实不会发生之前模拟实现string的时候发生的无符号整型最大值的问题。

在这里插入图片描述
大家看这样子去测试就出问题了
在这里插入图片描述
注意,func(v1)是两次读取
程序运行时崩了。
在这里插入图片描述

先简单分析一下,这可能时内存问题,可能时数组越界。
为什么push_back 5次的时候没问题,push_back 4次就出问题了?
5个和4个的区别是什么?

insert的时候扩容出问题了。

注意看
在这里插入图片描述
在这里插入图片描述
发生了什么,扩容之后,start和finish变了,start 和finish为什么会变?
这是我们遇见的最经典的迭代器失效的问题。

pos变成野指针了。
这也就导致一直在循环的问题。
在这里插入图片描述
那这个怎么解决呢?
更新一下pos.

//void insert(iterator pos, const T& val)
iterator insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后更新pos,解决pos失效的问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

接下来,继续问一个问题,看前面的测试图片,insert之后能不能修改迭代器的位置。

(*pos)++;//我想修改这个3的位置。
func(v1);

在这里插入图片描述
程序没有报错,但是很明显不符合我们的预期。为什么?
不是已经把pos更新了吗,为什么外面还是不行呢?怎么不起作用。
因为自己写的insert是传值形参,形参的改变 不会影响实参的改变 。

怎么解决?

能不能用引用传参解决,看起来不错,实际不好。用引用传参会报错,为什么?
在这里插入图片描述

insert 引起迭代器失效的两种情况:
1.野指针问题
2.意义已经变了

通过返回值去解决
但是最好别用,你也不知道是什么时候失效。
insert以后我们认为pos失效了,不能再使用。

erase

在这里插入图片描述
现在又有一个问题,erase以后pos会不会失效?
不会,但是库里面的失效了,并且vs报错非常强烈,看下面。
在这里插入图片描述
报了一个断言错误。

再看一下g++下的运行。
在这里插入图片描述

那到底erase以后pos失效还是不失效?vs比较合理还是g++合理?
如果pos位置是4呢,那这个位置就很不合理了。

所以我们认为失效,不要访问,行为结果未定义,跟具体的编译器有关。
一定要注意,不然会被坑的很惨。

要想解决一下这种情况,我们都是用返回值来处理一下,其实本质就是不要pos跳过。

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}

下面这个测试,连续的偶数和最后一个是偶数都能解决,就没什么大问题了。
在这里插入图片描述

1. vs进行强制检查,erase以后,迭代器不能访问。
2.g++没有强制检查,具体问题具体分析,结果未定义。

string有没有迭代器失效?
有,但是string不容易发生迭代器失效问题。
insert和erase不喜欢用迭代器,用的都是下标。

析构函数

~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}

下面的构造函数。
在这里插入图片描述

给大家看一个大坑。
先看下面的代码,这样写有没有什么问题?
在这里插入图片描述
程序崩了

不初始化会出各种问题。
一调试很容易看到会出现哪些问题。

加上初始化列表

vector(size_t n, const T& val = T())//T()是什么前面已经讲得很清楚了: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}

看一下这个构造函数。
在这里插入图片描述
在这里插入图片描述
如果是用iterator就写死了,你必须用vector的迭代器来进行初始化。

迭代器区间初始化,是必须用vectro的迭代器初始化吗?
一个容器用迭代器区间初始化,需要时任意类型的迭代器。

这里引出了另一个语法,允许一个类的成员函数再是函数模板。

// [first, last)
template <class InputIterator>
vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{while (first != last) //不能用 <=,如果是链表肯定就不行{push_back(*first);++first;}
}

如果我们不写初始化列表,可用c++11的语法,成员变量声明的时候给个缺省值
缺省值是给构造函数的初始化列表用的。
在这里插入图片描述

在这里插入图片描述

奇怪的事情发生了,编译的时候报了一个这样的错误
在这里插入图片描述

为什么匹配错了,匹配到上面写的那个迭代区间初始化那个?
在这里插入图片描述

我们知道编译器会调用最匹配的那个,仔细分析一下其实可以发现,如果推演一下的话,如果要匹配 vector(size_ t n, const T& val =T()); 的话,就会发生类型转换,而调用迭代器区间初始化的话,进行推演,如果类型是int 直接就匹配上了。

然后看迭代器区间初始化的代码,int不能解引用,就直接报错了。

怎么解决?
1.加个u, 代表我这个变量是无符号。
在这里插入图片描述

2…看STL的源码,看源码是怎么解决这个问题的。
我们这里可以用一个很简单的方式去就解决,提供一个重载的版本。

引用返回时有风险的,要谨慎使用,除非你想想operator【】那样,可以去修改。

给大家看一下神奇的东西。
在这里插入图片描述
只要类型能匹配,char可以发生转换。

最神奇的还是可以这么玩。
在这里插入图片描述

甚至可以是数组,为什么可以是数组?
原生指针可以是天然的迭代器,有个前提,这个原生指针是指向数组。
其实vector的迭代器,string的迭代器也可以是原生指针。

接着扩展一下。
sort

默认是升序
在这里插入图片描述
这是一个函数模板,它的名字叫随机访问迭代器,那随机访问是什么呢?一般底层是数组。
在这里插入图片描述
它可以帮我们排序,并且用起来很爽。

如果是降序,我们就这么用。
1.
在这里插入图片描述
2.
在这里插入图片描述
这两个其实是等价了。

拷贝构造

我们还涉及深浅拷贝的问题。
在这里插入图片描述
首先这样写,是我们经典的浅拷贝的问题。

我们先写一个传统写法的深拷贝。
在这里插入图片描述

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

看下面的代码,崩了,为什么?
在这里插入图片描述

在这里插入图片描述
也就是说当我们数据是int的时候程序能正常运行,当我们的数据是string的时候就崩的。

因为memcpy也是一种浅拷贝。调用拷贝构造的时候,memcpy干了什么事情。
从起始位置开始依次把所有值拷贝下来。

在这里插入图片描述
这里面还有一层是我们没有考虑的,这是深拷贝里面又套了一层深拷贝。memcpy就是深层次的浅拷贝。
调用析构的时候会崩。

怎么解决呢?
我们肯定是要解决三个问题,数据是int类型,或者string类型,或者vector的vector类型。

怎样完成深拷贝呢?难道是我们自己写吗?
我们不能自己解决,因为T是模板,我们也不知道它具体是什么类型。
不能自己给它写一个深拷贝,因为它们是私有的,动不了里面的。

所以我们这里面调一个深拷贝的函数来完成。
赋值就是深拷贝。
在这里插入图片描述

其实我们还没有完全解决所有的问题 ,除了拷贝构造用了memcpy,扩容也用了memcpy。

修改扩容的代码。

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T)*size());for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}

那接着,哪里还有问题呢?vector里面的数据比如说是vector对象呢,比如vectro<vector >,给大家看一下,一个杨辉三角的例子。
在这里插入图片描述
测试
在这里插入图片描述

在这里插入图片描述
出错了,为什么?还有什么问题没有解决。
外面的vector是深拷贝,里面的vector是浅拷贝。
问题还是出现在拷贝构造,我们并没有自己写赋值。所以编译器还是用的默认生成的,是浅拷贝。

我们自己写个赋值就解决了。
在这里插入图片描述

现代写法
直接复用。

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
vector(const vector<T>& v)
{vector<T> tmp(v.begin, v.end());swap(tmp);
}

最后一个小问题,不加模板参数语法上也允许,但是不推荐这样写。
在这里插入图片描述

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

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

相关文章

Docker的安装与简单操作命令

目录 前言 docker的安装 基础docker操作 容器管理 镜像管理 容器镜像封装与加载 前言 前文简单说明了容器技术出现的背景&#xff0c;与对docker做了结构上的介绍Container容器技术简介-CSDN博客https://blog.csdn.net/qq_72569959/article/details/134814887 讲到dock…

研究前沿| Nat Communi:大豆节间距调控基因RIN1的分子机制解析

引言 株型是决定作物产量的重要性状。以半矮秆利用和提高氮肥利用效率为标志的绿色革命提高了作物抗倒伏能力&#xff0c;使水稻、小麦等作物可以通过密植提高单产&#xff0c;产量大幅提升。但是&#xff0c;大豆绿色革命基因尚未发现&#xff0c;与水稻和小麦产量提升相比&am…

如何用CHAT了解历史?

问CHAT&#xff1a;古代有什么常见的简单机械&#xff1f; CHAT回复&#xff1a; 1. 滑轮&#xff1a;滑轮是一种简单的机械&#xff0c;主要提供力量放大、改变力的方向等功能。在古代&#xff0c;人们使用滑轮来托起重物&#xff0c;如水井的提水装置&#xff0c;建造大型建…

大模型元年压轴盛会定档12月28日,第十届WAVE SUMMIT即将启航

文章目录 1. 前言2. WAVE SUMMIT五载十届&#xff0c;AI开发者热血正当时3. 酷炫前沿、星河共聚&#xff01;大模型技术生态发展正当时 1. 前言 回望2023年&#xff0c;大语言模型或许将是科技史上最浓墨重彩的一笔。从技术、产业到生态&#xff0c;大语言模型在突飞猛进中加速…

使命召唤9缺少buddha.dll的解决方法分享,如何快速修复buddha.dll

《使命召唤》系列作为备受欢迎的第一人称射击游戏,经常会在新作发行后引起广大玩家的讨论。最近&#xff0c;《使命召唤9》玩家中出现了一个常见的技术问题&#xff1a;游戏无法启动&#xff0c;因为系统找不到 buddha.dll 文件。这篇文章将探讨 buddha.dll 的相关信息、丢失原…

软件外包的 20 个问题以及如何避免这些问题

外包很常见。 三分之二的企业进行外包。全国范围内&#xff0c;以某种身份从事自由职业。这意味着全国 40% 的劳动力是个体户或从事零工。 客户支持、软件开发和营销是最常见的外包职能。 外包可以节省成本、提高速度和灵活性。 但这并非没有问题。外包的常见问题最终可能会…

gdb本地调试版本移植至ARM-Linux系统

移植ncurses库 本文使用的ncurses版本为ncurses-5.9.tar.gz 下载地址&#xff1a;https://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz 1. 将ncurses压缩包拷贝至Linux主机或使用wget命令下载并解压 tar-zxvf ncurses-5.9.tar.gz 2. 解压后进入到ncurses-5.9目录…

智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸡群算法4.实验参数设定5.算法结果6.参考文献7.MA…

arcgis for js 添加自定义叠加图片到地图坐标点上

在使用arcgis for js开发地图绘制图层时&#xff0c;可以通过相关api实现添加图标到某个坐标点&#xff0c;那么如果现在有一个需要添加一个小图叠大图的需求&#xff0c;又或者是自定义绘制图标&#xff0c;如何实现&#xff1f; 1、简单地绘制一个图标到底图图层上面 const…

C++STL之List的实现

首先我们要实现List的STL,我们首先要学会双向带头链表的数据结构。那么第一步肯定是要构建我们的节点的数据结构。 首先要有数据域&#xff0c;前后指针域即可。 再通过模板类进行模板化。 然后再写List的构造函数&#xff0c;这个地方用T&,通过引用就可以减少一次形参拷…

vue2-elementUI部分组件样式修改

el-radio样式&#xff1a; /deep/ .el-radio__input .el-radio__inner {width: 20px;height: 20px;position: relative;cursor: pointer;-webkit-appearance: none;-moz-appearance: none;appearance: none;border: 1px solid #999;border-radius: 0;outline: none;transition…

阿里云cdn设置相同的域名路径访问不同的oss目录

1.设置回源配置&#xff0c;添加回源URL改写 2.设置跨域&#xff0c;cdn的跨域优先oss 3.回源设置

电商早报 | 12月12日| 淘宝公布2023年度商品初选名单入围

淘宝公布2023年度商品初选名单&#xff1a;军大衣、酱香拿铁、熊猫周边入围 又一年临近收官&#xff0c;淘宝如期启动了“2023年度十大商品”评选。 12月11日&#xff0c;淘宝官方发布了初选入围名单&#xff0c;30件最具代表性的商品脱颖而出。据淘宝路边社介绍&#xff0c;…

784. 字母大小写全排列

字母大小写全排列 描述 : 给定一个字符串 s &#xff0c;通过将字符串 s 中的每个字母转变大小写&#xff0c;我们可以获得一个新的字符串。 返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。 回文串 是正着读和反着读都一样的字符串。 题目 : LeetCode 784. 字母…

10、RocketMQ的Comsumer的消息队列的分配

前置知识&#xff1a;RocketMQ的topic存在多个队列&#xff0c;而多个topic分配在同一消费组里面&#xff0c;消费组里面存在多个消费者&#xff0c;当消费者注入到消费组时要进行消费者与多个队列之间的分配&#xff0c;而这种分配被称之为Rebalance机制&#xff0c;该机制的本…

Linux命令大全(全网最细讲解)

文章目录 一、基础知识&#xff08;1&#xff09; Linux系统的文件结构&#xff08;2&#xff09; Linux系统命令行的含义&#xff08;3&#xff09;命令的组成二、基础操作&#xff08;1&#xff09; 关闭系统&#xff08;2&#xff09; 关闭重启&#xff08;3&#xff09; 帮…

节日问候:在 Metaverse 中一起庆祝节日!

冬季即将来临&#xff0c;节日的脚步也越来越近&#xff0c;是时候通过 The Sandbox 中的最新活动——“节日问候”来迎接节日气氛了&#xff01;为期 43 天的庆祝活动从 12 月 11 日开始&#xff0c;到 1 月 22 日结束&#xff0c;将带领玩家穿越一个充满 60 种体验的冬季仙境…

d2l绘图不显示的问题

之前试了各种方法都不行 在pycharm中还是不行&#xff0c;但是在anaconda中的命令行是可以的 anaconda prompt conda activaye py39 #进入f盘 F: #运行文件 python F:\python_code\softmax.py

FreeRTOS的三处栈空间设置分析

1、汇编启动代码中设置栈 这个栈空间只有300字节&#xff0c;是用于汇编启动代码早期&#xff0c;以及调用C语言的main函数&#xff08;创建任务等&#xff09;在创建好任务&#xff0c;启动调取器后&#xff0c;这个栈空间就被抛弃掉&#xff0c;后续不会使用到等调度器开启后…

深入理解Dubbo-5.服务注册源码分析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…