C++【STL容器系列(二)】vector的模拟实现

文章目录

  • 1. vector的结构
  • 2. vector的默认成员函数
    • 2.1构造函数
      • 2.1.1 默认构造
      • 2.1.2 迭代器构造
      • 2.1.3 用n个val初始化构造
    • 2.2 拷贝构造
    • 2.3 析构函数
    • 2.4 operator=
  • 3. vector iterator函数
    • 3.1 begin 和 cbegin函数
    • 3.2 end() 和 cend()函数
  • 4. vector的小函数
    • 4.1 size函数
    • 4.2 capacity函数
    • 4.3 swap函数
  • 5. vector的访问函数
    • 5.1 operator[]
    • 5.2 front
    • 5.2 back
  • 6 vector的增删改
    • 6.1 reserve
    • 6.2 push_back(尾插)
    • 6.3 pop_back(尾删)
    • 6.4 insert(在任意位置插入数据)
    • 6.5 erase(删除数据)
    • 6.6 clear(清除数据)
    • 6.7 empty(判空)
  • 结语

1. vector的结构

我们通过查看vector的底层,能发现结构并不是我们所想的数组,_size,_capacity
在这里插入图片描述
而是由三个指针所组成的,这三个指针分别为;start:开始元素finish:末尾元素的后一个位置end_of_storage:容量(iterator是在前面typedef过的,原型为 T*)

我们前面讲结构,vector是通过模板来实现的,但模板并不能分离成两个文件,所以本次模拟只会有"vector.h"这个头文件。

由于我们是模拟实现,那么底层结构也要是三个指针

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

2. vector的默认成员函数

2.1构造函数

		vector(); //默认构造template <class InputIterator>vector(InputIterator first, InputIterator last) //迭代器构造vector(size_t n, const T& x = T()) //用n个val初始化构造

2.1.1 默认构造

其实默认构造我们并不需要写,因为我们在声明成员变量的时候就已经给了缺省值nullptr,所以写不写都无所谓,用编译器生成的默认构造就可以了,但是如果你写了一个构造函数,那么编译器就不会生成默认构造,这时候就可以使用C++11引入的default,来让编译器生成默认构造。

vector() = default; //让编译器生成默认构造

2.1.2 迭代器构造

这里其实并不复杂,push_back迭代器所代表的内容就好了。

但问题是:为什么要在多写一个模板出来呢?

答案也很简单,因为外面的模板和这个模板推导的类型不一样,外面是推导vector<T>中的T,这个是推导整个vector<T>

拿vector<int>举例,外面模板类型是 int,里面模板类型是vector<int>

		template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

2.1.3 用n个val初始化构造

这个也很简单,用一个循环来push_back(val)就好了

		vector(size_t n, const T& x = T()){reserve(n); for (size_t i = 0; i < n; ++i){push_back(x);}}
  • reserve: 用于提前开好空间,这样就不用反复扩容了

但是这样写会有问题,我使用其他类型不会报错,但是使用vector<int>(n,val)的时候就会报错!!!
在这里插入图片描述
竟然调用了迭代器初始化的构造函数,这是为什么呢,原因就在模板这里。

调用函数的机制是只要类型匹配,怎么简单怎么来,由于用n个val初始化是模板参数(const T& x = T()),迭代器构造也是模板参数,但是用n个val初始化还有一个 size_t 的参数,那么模板推导后有需要类型转换,势必会麻烦一点,而迭代器是两个模板参数,推导完可以直接使用,所以编译器在调用函数的时候,自然而然就会调用迭代器版本的构造。

解决方法也很简单,就是自己再写一个 int 版本的构造(其实库里也是这样实现的)
在这里插入图片描述

所以我们实现的时候,要多实现一个整形版本的构造(这里只实现了int版本的)

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

2.2 拷贝构造

拷贝构造其实也很简单,不需要局限于传统写法和现代写法,使用范围for就能完美解决拷贝构造。

vector(const vector& v)
{reserve(v.capacity());for (auto& e : v){push_back(e);}
}

2.3 析构函数

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

2.4 operator=

在完成operator=的时候,现代写法是目前最好的写法;传参的时候不传引用,而是传值传参(这样会进行拷贝构造,而拷贝构造就完成了形参的深拷贝),然后再交换形参。

vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

3. vector iterator函数

还是和string一样,只实现iteratorconst_iterator这两个版本

		typedef T* iterator;typedef const T* const_iterator;

3.1 begin 和 cbegin函数

		iterator begin(){return _start;}const_iterator begin() const{return _start;}

3.2 end() 和 cend()函数

		iterator end(){return _finish;}const_iterator end() const{return _finish;}

4. vector的小函数

4.1 size函数

左闭右开,一减就是元素个数

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

4.2 capacity函数

只是被减数的不同,返回容量数

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

4.3 swap函数

调用库里面的swap函数,避免进行深拷贝。

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

5. vector的访问函数

5.1 operator[]

直接返回该位置元素的引用

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

5.2 front

返回第一个元素的引用

		T& front(){return *_start;}const T& front() const{return *_start;}

5.2 back

返回最后一个元素的引用

		T& back() {return *(_finish - 1);}const T& back() const{return *(_finish - 1);}

6 vector的增删改

6.1 reserve

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

注意:我们需要在delete[] _start之前存储原本的数据个数,不然会出问题。
如果我们delete在刷新_finish,那么就会用到size()这个函数,但size这个函数是_start - _finish,由于_start这整个空间已经被释放了(_star、_finish、_end_of_storage是一块空间的不同位置),所以我们需要在释放_start之前先存储原来的数据个数size_t old_size = size();,再用old_size来刷新_finish。

但是这会有一个问题,如果我的vector是自定义类型且内部自己有申请空间,这时候扩容就会出问题,因为memcpy是浅拷贝,也就是一个字节一个字节的拷贝,这样虽然tmp是new出来的新空间,但是经过memcpy,_start 和 tmp就会指向同一个空间,delete[] _start,就相当于把tmp也delete了,这时候在尾插,析构的时候程序就会崩溃掉。
在这里插入图片描述
解决方法就是调用operator=,这样自定义类型就会调用自己的operator=。

		void reserve(size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//浅拷贝 -> 遇到有申请空间的对象出问题//memcpy(tmp, _start, size() * sizeof(T));//深拷贝 调用他们自己的 operator=for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}}

6.2 push_back(尾插)

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

6.3 pop_back(尾删)

void pop_back()
{assert(!empty());_finish -= 1;
}

6.4 insert(在任意位置插入数据)

iterator insert(iterator pos, const T& val)
{assert(pos >= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

这里和扩容后的size同理,括完容后要更新pos,要提前记录pos与_start之间的距离。
使用完insert会造成迭代器失效,解决方法:使用完这个函数后,认为这个迭代器已经失效,不要使用

6.5 erase(删除数据)

iterator erase(iterator pos)
{assert(pos <= end());if (pos == end()){pop_back();}else{iterator end = pos;while (end != _finish){*end = *(end + 1);++end;}--_finish;}return pos;
}

用后面的数据覆盖前面就好了。
这个函数同样会造成迭代器失效,解决方法和 insert 一样。

6.6 clear(清除数据)

void clear()
{_finish = _start;
}

6.7 empty(判空)

bool empty() const
{return _finish == _start;
}

结语

那么这次的分享就到这里结束了~
最后感谢您能阅读完此片文章~
如果您认为这篇文章对你有帮助的话,可以用你们的手点一个免费的赞并收藏起来哟~
如果有任何建议或纠正欢迎在评论区留言~
也可以前往我的主页看更多好文哦(点击此处跳转到主页)。

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

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

相关文章

Linux开发讲课49--- Linux 启动过程分析

理解运转良好的系统对于处理不可避免的故障是最好的准备。 启动过程非常简单。内核在单核上以单线程和同步状态启动&#xff0c;似乎可以理解。但内核本身是如何启动的呢&#xff1f;initrd&#xff08;initial ramdisk&#xff09; 和引导程序(bootloader)具有哪些功能&#…

vscode中执行git合并操作需要输入合并commit信息,打开的nano小型文本编辑器说明-

1.前提: VScode中的git组件执行任何合并动作的时候需要提交远程合并的commit信息,然后编辑器自动打开的是nano文本编辑器 2.nano编辑器说明: 1.保存文件:按 Ctrl + O,然后按 Enter 来保存文件。 2.退出编辑器:按 Ctrl + X,这会退出 nano。 3.剪切文本:移动光标到要剪…

Java 并发相关集合

文章目录 一、CopyOnWriteArrayList 源码1.1. 概述1.2. 思想1.3. 源码① 数据结构② 初始化③ 添加元素④ 获取元素⑤ 删除元素 二、ArrayBlockingQueue 源码2.1. 概述2.2. 思想2.3. 源码① 数据结构② 初始化③ 阻塞式获取和新增元素④ 非阻塞式获取和新增元素⑤ 指定超时时间…

AutoDL使用简记

AutoDL使用简记 一、前言二、AutoDL显卡配置、价格简介2.1显卡配置及价格2.2计费方式的种类2.3开通会员及优惠 三、AutoDL使用教程3.1选择深度学习架构3.2文件传输3.3运行程序 一、前言 在进行深度学习模型训练时&#xff0c;通常会面临本地显卡显存或者运行速度的不足&#x…

基于STM32智能电流表

采用STM32F103C8T6微控制器为核心&#xff0c;设计了一款精密的电流表。该电流表通过精确采集采样电阻上的分压信号&#xff0c;并进行信号放大处理&#xff0c;随后利用ADC&#xff08;模数转换器&#xff09;高效地捕获放大后的电压信号&#xff0c;通过一系列算法运算&#…

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…

支持 Win10 的网络环境模拟(丢包,延迟,带宽)

升级 Windows 10 以后&#xff0c;原来各种网络模拟软件都挂掉了&#xff0c;目前能用的就是只有 clumsy&#xff1a; 唯一问题是不支持模拟带宽&#xff0c;那么平时要模拟一些糟糕的网络情况的话&#xff0c;是不太方便的&#xff0c;而开虚拟机用 Linux tc 或者设置个远程 l…

网页web无插件播放器EasyPlayer.js点播播放器遇到视频地址播放不了的现象及措施

在数字媒体时代&#xff0c;视频点播已成为用户获取信息和娱乐的重要方式。EasyPlayer.js作为一款流行的点播播放器&#xff0c;以其强大的功能和易用性受到广泛欢迎。然而&#xff0c;在使用过程中&#xff0c;用户可能会遇到视频地址无法播放的问题&#xff0c;这不仅影响用户…

.NET周刊【11月第2期 2024-11-10】

国内文章 .NET 全能高效的 CMS 内容管理系统 https://www.cnblogs.com/1312mn/p/18511224 SSCMS 是一个完全开源的企业级内容管理系统&#xff0c;基于 .NET Core 开发&#xff0c;适合跨平台部署。其特点包括支持多终端发布和功能插件&#xff0c;具有完善的权限控制和安全…

Pytorch从0复现worc2vec skipgram模型及fasttext训练维基百科语料词向量演示

目录 Skipgram架构 代码开源声明 Pytorch复现Skip-gram 导包及随机种子设置 维基百科数据读取 建立词频元组列表并根据词频排序 建立词频字典,word_id字典,id_word字典 二次采样 正采样与负采样 Skipgram模型类 模型训练 词向量输出 近义词寻找 fasttext训练Skip-…

如何详细查询全球药品研发的进度信息?

药品的研发进展对于医药研发人员来说&#xff0c;不仅是知识和技能的积累&#xff0c;更是职业精神和价值观的塑造。通过了解药品的研发进展&#xff0c;研发人员可以更好地提高自己的专业知识和技能&#xff0c;激发创新思维&#xff0c;保持专业竞争力&#xff0c;提高研发效…

从0学习React(11)

1. 引言 上个星期的工作内容是写IT资产管理的前端页面。其实&#xff0c;尽管我之前有一些前端开发的经验&#xff0c;但并不是很多。这次让我独立完成一个页面的开发&#xff0c;刚开始时我感到无从下手。 2. 初期的困惑和焦虑 我记得在星期一和星期二的时候&#xff0c;那…

第3章 需求 3.3需求的有效传递与度量

3.3 需求的有效传递与度量 收集需求是需要投入很多工作量的&#xff0c;同时需求必须有效传递到产品端才能最终发挥价值。而需求的有效传递却是一个容易被忽视的环节。 现实中存在各种需求传递方式&#xff0c;如口头传递、邮件传递、会议传递等&#xff0c;但这些需求都未被统…

Vue2中使用firefox的pdfjs进行文件文件流预览

文章目录 1.使用场景2. 使用方式1. npm 包下载,[点击查看](https://www.npmjs.com/package/pdfjs-dist)2. 官网下载1. 放到public文件夹下面2. 官网下载地址[点我,进入官网](https://github.com/mozilla/pdf.js/tags?afterv3.3.122) 3. 代码演示4. 图片预览5. 如果遇到跨域或者…

vue3+vite 前端打包不缓存配置

最近遇到前端部署后浏览器得清缓存才能出现最新页面效果得问题 所以…按以下方式配置完打包就没啥问题了&#xff0c;原理很简单就是加个时间戳 /* eslint-disable no-undef */ import {defineConfig, loadEnv} from vite import path from path import createVitePlugins from…

RS485/RS422保护电路

由于GJB 151B没有雷击和浪涌测试要求&#xff0c;故不需要防雷器件。TVS管使用SMB6.5CA&#xff0c;共模电感选择LCHWCM-453228-510YT01&#xff0c;详细设计电路如下图所示&#xff0c;此设计可同时满足GJB 151B和DO 160G的标准。注意SMB封装的TVS管是600W&#xff0c;SMA封装…

CKA认证 | Day1 k8s核心概念与集群搭建

第一章 Kubernetes 核心概念 1、主流的容器集群管理系统 容器编排系统&#xff1a; KubernetesSwarmMesos Marathon 2、Kubernetes介绍 Kubernetes是Google在2014年开源的一个容器集群管理系统&#xff0c;Kubernetes简称K8s。 Kubernetes用于容器化应用程序的部署&#x…

《大模型应用开发极简入门》笔记

推荐序 可略过不看。 初识GPT-4和ChatGPT LLM概述 NLP的目标是让计算机能够处理自然语言文本&#xff0c;涉及诸多任务&#xff1a; 文本分类&#xff1a;将输入文本归为预定义的类别。自动翻译&#xff1a;将文本从一种语言自动翻译成另一种语言&#xff0c;包括程序语言。…

在AutoDL上部署一个自定义的Python环境并在pycharm上使用

#AutoDL #GPU #租显卡 如何在AutoDL上部署一个自定义的Python环境 下面将会给出如何在AutoDL部署一个自定义的Python环境的详细步骤&#xff0c;希望可以帮助到同样对于显卡具有需求的同学。 注册账号 首先登陆AutoDL官网&#xff1a;https://www.gpuhub.com/register 链接…

高级AI记录笔记(二)

学习位置 B站位置&#xff1a;红豆丨泥 UE AI 教程原作者Youtube位置&#xff1a;https://youtu.be/-t3PbGRazKg?siRVoaBr4476k88gct素材自备 提前将动画素材准备好 枪的武器插槽位置调整好 动画蓝图基本没什么变化 准备武器 在AI的接口蓝图中添加两个函数一个是装备武…