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.rhkb.cn/news/215677.html

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

相关文章

网易有道强力开源中英双语语音克隆

项目地址&#xff08;基于PromptTTS&#xff09;&#xff1a; https://github.com/netease-youdao/EmotiVoice EmotiVoice Docker镜像 尝试EmotiVoice最简单的方法是运行docker镜像。你需要一台带有NVidia GPU的机器。先按照Linux和Windows WSL2平台的说明安装NVidia容器工具…

线上盲盒小程序,开启互联网盲盒时代

近年来&#xff0c;盲盒经济在国内非常火爆&#xff0c;各类盲盒品牌层出不穷&#xff0c;深受国内外年轻人、消费者的喜爱。 目前&#xff0c;根据数据显示&#xff0c;盲盒市场不仅在线下异常火热&#xff0c;线上盲盒也是成为了大众的新选择。各类电商平台中盲盒的成交额更…

使用node实现链接数据库并对数据库进行增删改查的后端接口

环境 node npm 编辑器 vscode 项目配置 新建目录 用vscode打开 终端输入 npm init -y npm install mysql npm install express 代码 安装好之后的代码页面 新建 在根目录新建api.js文件 const express require(express); const db require(./db/index); const app…

计算机考研408-计算机网络、操作系统整书知识点脑图

计算机网络、操作系统整书知识点脑图 今天突然想起来考研期间为了方便记忆&#xff0c;费了很大力气整理了计算机网络、操作系统两本书知识点的脑图&#xff0c;想着放着也没啥用&#xff0c;分享出来给大家看看 但是思维导图格式的东西好像没法直接发成文章&#xff0c;上传…

使用Windows10的OneDrive应用程序,让文件管理上一个台阶

这篇文章解释了如何通过在文件资源管理器和OneDrive应用程序之间轮换&#xff0c;将OneDrive与Windows 10一起使用。 使用文件资源管理器进行组织 你不必将所有OneDrive文件都保存在硬盘上&#xff0c;事实上&#xff0c;你可以将任意数量的文件留在云中&#xff08;也就是微…

SpringBoot-Swagger3

SpringBoot——2.7.3版本整合Swagger3-CSDN博客文章浏览阅读5.4k次&#xff0c;点赞6次&#xff0c;收藏17次。Swagger2&#xff08;基于openApi3&#xff09;已经在17年停止维护了&#xff0c;取而代之的是 sagger3&#xff08;基于openApi3&#xff09;&#xff0c;而国内几乎…

C++STL之List的实现

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

机械中常用的一些术语

目录 一、OEMSOP:SOP编写指南 WI(标准作业指导书):标准作业程序 &#xff08;SOP&#xff09;:SOP和WI的区别&#xff1a;一、PFC、FMEA、PCP、WIPPAP、PSW&#xff1a;APQP&#xff1a;BOM&#xff08;Bill of Material&#xff09;物料清单DV&#xff08;设计验证&#xff09…

我的创作三周年纪念日

今天收到CSDN官方的来信&#xff0c;创作三周纪念日到了。 Dear: Hann Yang &#xff0c;有幸再次遇见你&#xff1a; 还记得 2020 年 12 月 12 日吗&#xff1f; 你撰写了第 1 篇技术博客&#xff1a; 《vba程序用7重循环来计算24》 在这平凡的一天&#xff0c;你赋予了它…

智能建筑市场调研:预计2028年将达到10736亿元

我国智能建筑起源于20世纪90年代&#xff0c;在我国发展了二十年&#xff0c;行业经历了初创期、规范期、发展期三个阶段&#xff0c;已经形成了产业规模及产业链&#xff0c;智能建筑工程已经普及到了各种类型建筑并延伸到了城市建设及相关行业。地域上&#xff0c;智能建筑由…

LeetCode(55)环形链表【链表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 环形链表 1.题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评…

100V降压5V芯片

高效能100V降压5V芯片&#xff1a;9V至100V输入电压范围&#xff0c;适用于各类应用 在当今的电子设备中&#xff0c;电源管理起着至关重要的作用。一款高效、稳定、可靠的电源芯片&#xff0c;是保证设备正常运行的关键。今天&#xff0c;我们为大家介绍一款性能卓越的100V降…

d2l绘图不显示的问题

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

Python数据科学视频讲解: 基本输出函数 print( )函数

2.4 基本输出函数&#xff1a;print()函数 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解2.4节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵盖数据科学应用的全流程&…

视频汇聚/音视频流媒体视频平台/视频监控EasyCVR分享页面无法播放,该如何解决?

国标GB28181安防视频监控/视频集中存储/云存储EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统…

处理器的状态管理

在超标量处理器内部有两个状态, Architecture State 指令集定义的状态&#xff0c;例如通用寄存器的值、PC值以及存储器的值等&#xff1b;Speculative State 超标量处理器内部的状态,例如重命名使用的物理寄存器、重排序缓存(ROB)、发射队列(Issue Queue)和Store Buffer等部件…

Vue2与Vue3的语法对比

Vue2与Vue3的语法对比 Vue.js是一款流行的JavaScript框架&#xff0c;通过它可以更加轻松地构建Web用户界面。随着Vue.js的不断发展&#xff0c;Vue2的语法已经在很多应用中得到了广泛应用。而Vue3于2020年正式发布&#xff0c;带来了许多新的特性和改进&#xff0c;同时也带来…

Halcon参考手册语义分割和边缘提取知识总结

1.1 语义分割和边缘提取介绍 通过语义分割&#xff0c;我们使用深度学习(DL)网络将输入图像的每个像素分配给一个类。 图(1)语义分割示例 在图(1)中&#xff0c;输入图像的每个像素都被分配给一个类&#xff0c;但是苹果的三个不同实例和橘子的两个不同实例都不是可区分的对象…

QT之常用按钮组件

QT之常用按钮组件 导入图标 布局 显示选中 实验结果 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }void Widget::on_push…

ArcGIS无法绘制一个或多个图层

背景&#xff1a;在导入一份数据时候&#xff0c;arcmap出现无法绘制一个或多个图层的错误&#xff0c;...点数少于要素所要求的的数量&#xff0c;查阅了半天资料发现是制作数据时候拓扑关系错误造成&#xff0c;现将处理方法详细记录如下&#xff1a; 1.原数据&#xff1a; …