C++STL详解(四)——vector类的具体实现

在上篇文章中,我们已经学习了vector的具体接口使用方法,在本篇文章中,我们将学习实现一个vector容器。

目录

一.vector各函数接口总览

二.vector当中的私有成员

三.默认成员函数

3.1构造函数

3.1.1构造函数1

3.1.2构造函数2

3.1.3构造函数3

3.2拷贝构造函数

3.3赋值运算符重载函数

3.4析构函数

四.迭代器相关函数

4.1begin和end

4.2rbegin和rend

五.容量与大小相关函数

5.1size和capacity

5.2reserve

5.3resize

5.4empty

六.修改容器内容相关函数

6.1push_back

6.2pop_back

6.3insert

6.4erase

6.5swap

七.访问容器相关函数


一.vector各函数接口总览

由于标准库中也有vector类,因此我们模拟实现需要放在自己的命名空间内部。

namespace trousers
{template<class T>class vector{public://迭代器typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector();vector(size_t n, const T& val);template <class InputIterator>//需要使用模板,传入的迭代器的类型不定义。(可以通过其他类型容器进行初始化)vector(InputIterator first, InputIterator last);//迭代器区间构造vector(const vector<T>& V);//拷贝构造vector<T>& operator=(const vector<T>& v);//赋值运算符重载~vector;//迭代器相关函数//正向迭代器iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//反向迭代器iterator rbegin();iterator rend();//容量与大小相关函数size_t size() const;size_t capacity() const;void reserve(size_t n);void resize(size_t n, const T& val = T());bool empty()const;//修改容器内容相关函数void push_back(const T& x);void pop_back();void insert(iterator pos, const T& x);iterator erase(iterator pos);void swap(vector<T>& v);//访问相关函数T& operator[](size_t i);T& operator[](size_t i) const;private:iterator _start;//指向容器头iterator _finish;//指向有效数据尾iterator _end_of_storage;//指向容器空间的尾};
}

二.vector当中的私有成员

在之前的顺序表博客中提到过,我们的容器是先开空间,再往空间内填充数据的。因此我们的有效数据尾和容器空间尾可能并不相同。

假如现在有这么一个容器,开了9个空间,但是只有6个空间内填充了数据。

 那么,我们的_start指向的就是容器的头部,_finish指向的是填充了数据的最后一个空间。而_end_of_storage指向的是空间的尾部。

三.默认成员函数

3.1构造函数

我们在这里实现3个vector的构造函数,分别是:

		vector();vector(size_t n, const T& val);template <class InputIterator>//需要使用模板,传入的迭代器的类型不定义。(可以通过其他类型容器进行初始化)vector(InputIterator first, InputIterator last);//迭代器区间构造

3.1.1构造函数1

首先,我们完成第一个无参的构造函数。

在此处,我们直接将构造的对象的三个私有成员全部置为空指针即可。

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

3.1.2构造函数2

该构造函数可以将构造的对象初始化为由n个val构成的容器。

这个函数要用到reserve和push_back,我们在本文的后续小节中介绍这两个函数的实现,在这里我们直接使用。

这个函数的实现思路是:先使用reserve设置空间,然后使用一个循环将数据设置进去。

具体实现代码如下:

vector(size_t n, const T& val):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{reserve(n);//reserve内有对私有成员的更新,因此初始化列表可以置为nullptrfor (size_t i = 0; i < n; i++){push_back(val);}
}

该函数除了需要实现这个版本之外,还需要实现如下两个版本:

		vector(long n, const T& val):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);//reserve内有对私有成员的更新,因此初始化列表可以置为nullptrfor (long i = 0; i < n; i++){push_back(val);}}vector(int n, const T& val):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);//reserve内有对私有成员的更新,因此初始化列表可以置为nullptrfor (int i = 0; i < n; i++){push_back(val);}}

 我们可以发现,这两个重载函数的不同就是其参数n的类型不同,那么为什么我们要实现这两个版本呢?这是因为编译器无法确认参数的类型。

vector<int> v1(5,7);

在运行上面的这行代码时,编译器会优先调用构造函数3,而不会调用构造函数2.

这是因为5和7是同一个类型的,和构造函数3的参数列表最相近,因此我们会在此处调用构造函数3。因此,我们需要实现上述两个重载函数。

3.1.3构造函数3

vector还支持迭代器区间初始化,我们可以使用一个前闭后开的迭代器进行初始化。

因为迭代器区区间可以是任意容器的迭代器区间,因此我们需要将这个函数设置成一个函数模板,以确保可以使用任意类型的迭代器初始化该容器。

而这个函数的实现逻辑和第二个构造函数的实现方式基本上是相同的。

		template <class InputIterator>//需要使用模板,传入的迭代器的类型不定义。(可以通过其他类型容器进行初始化)vector(InputIterator first, InputIterator last)//迭代器区间构造:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){while (first != last){push_back(*first);//push_back中调用了reserve,可以用于设置私有成员++first;}}

3.2拷贝构造函数

拷贝构造函数的实现是极其简单的,我们直接一个数据一个数据的复制过去即可。

		vector(const vector<T>& V)//拷贝构造:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(V.capacity());for (auto e : V){push_back(e);}}

3.3赋值运算符重载函数

关于赋值运算符的重载可以有多种实现方法

实现方法1:

  1. 首先判断不能是自己给自己赋值
  2. 若是给自己赋值则不需要进行操作
  3. 若不是给自己赋值,则先清理掉原来的空间
  4. 之后开辟一块和容器v同样大的空间
  5. 然后将v中的数据一个一个拷贝过去
  6. 最后更新私有成员的值
		vector<T>& operator=(const vector<T>& v);//赋值运算符重载{if (this != &v)//判断{//清理数据delete[] _start;//开空间_start = new [v.capacity()];//复制数据for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];//_start[i]=v[i]   }//更新数据_finish = _start + size();_end_of_storage = _start + v.capacity();}return *this;//返回可以支持连续赋值。}

这个方法实现起来过于繁琐,我很讨厌写这种要很长的代码。所以我选择下面的实现方法

实现方法2:

首先,我们需要修改该函数的参数列表

		vector<T>& operator=(const vector<T> v);//赋值运算符重载

这里我们不选择引用传参,那么我们的编译器则会在此处形成一个会自动销毁的形参。

 该函数具体的运行逻辑如下:

  1. 交换这两个对象
  2. 交换后,两个对象的地址互换了。
  3. 此时,形参的地址是this的原地址。
  4. 销毁形参,也就把this指向的空间销毁了
  5. 形参被销毁,this的赋值也完成了

因此,我们实现起来仅仅只需要交换一下即可。

		vector<T>& operator=(const vector<T> v);//赋值运算符重载{swap(v);return *this;}

3.4析构函数

对于析构函数的实现很简单,我们直接将资源清理掉就可以了。

		~vector(){if (_start){delete[] _start;//更新数据_start = _end_of_storage = _finish = nullptr;}}

之所以判断_start是否为空,是因为如果为空的话可以少执行好多行代码。 

四.迭代器相关函数

4.1begin和end

begin()返回start,end返回_finish即可。

		iterator begin(){return _start;}iterator end(){return _finish;}

4.2rbegin和rend

不必多言,直接看图然后看代码。

		iterator rbegin(){return _finish - 1;}iterator rend(){return _start - 1;}

五.容量与大小相关函数

5.1size和capacity

这两个函数的实现是相当简单的。

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

5.2reserve

对于reserve的实现,我们要先了解reserve的规则:

  • 当n大于当前对象的capacity时,实现扩容
  • 当n小于当前对象的caoacity时,什么变化都不做

了解了reserve的规则了之后,我们就可以着手于实现reserve函数。

我们可以将实现分为如下几步:

  1. 判断当前n和capacity的大小
  2. 若大于,则开辟一块可以放置n个T数据的空间。
  3. 将原容器中的数据拷贝到该空间中
  4. 将原容器的空间释放
  5. 更新新空间的各个成员

具体的实现如下:

		void reserve(size_t n){size_t sz = size();if (n > capacity())//判断{T* tmp = new T[n];//开空间//拷贝for (size_t i = 0; i <sz ; i++){tmp[i] = _start[i];}//释放原空间delete[] _start;//更新数据_start = tmp;_finish = tmp + sz;_end_of_storage = _tmp + n;}}

 虽然,我们的reserve函数已经可以完成所有的功能了,但是,如果我们对0个数据的容器置为0的话,依旧需要走一次深拷贝。这就使性能下降,因此我们可以再加一个对该情况的处理。

				if (_start){//拷贝for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}//释放原空间delete[] _start;//因为_start中没有数据,因此该情况下在外部释放它其实是无用功。而我们外部重置了_start,因此我们可以将释放_start的的逻辑放在if内部}

5.3resize

首先,resize的规则是:

  • 当n大于当前的resize时,将size扩大到n,并将扩大的空间的数据设置为val
  • 当n小于当前的size时,截断掉即可。

根据resize的规则,我们可以得出实现该函数的步骤如下:

  1. 判断n的大小
  2. 如果n小于size,则更新finish
  3. 否则,则判断是否需要扩容
  4. 需要扩容则调用reserve,之后将扩大的空间设置为val即可。

具体的实现代码如下:

		void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish < _start + n){*_finish = val;_finish++;}}}

5.4empty

这个函数相当简单,我们只需要判断一下_start和_finish是否相同即可完成该函数

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

六.修改容器内容相关函数

6.1push_back

对于push_back的实现,我们的实现分为如下几个步骤:

  1. 判断容器的空间是否为满
  2. 若已满,则进行扩容
  3. 若未满,则将新数据插入到_finish处的位置
  4. 插入后,再将_finish的位置+1.

具体的实现代码如下:

		void push_back(const T& x){if (_finish = _end_of_storage)//判断{//扩容size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();//使用reserve函数进行扩容reserve(newcapacity);}*_finish = x;_finish++;}

6.2pop_back

对于数据的删除,我们使用的方法很简单,直接将_finish的位置往前移动1即可。

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

6.3insert

insert函数可以用于在指定位置插入数据。

我们实现的步骤如下:

  1. 首先判断是否需要扩容
  2. 将pos位置以及其后的数据整体后移
  3. 向pos位置插入val
		void insert(iterator pos, const T& x){if (_finish == _end_of_storage)//判断{  //扩容size_t newcapacity = capacity() == 0:4 ? 2 * capacity();reserve(newcapacity);}//后移iterator end = _finish;while (end>=pos+1)//移动本身则需要+1,不需要则不用+1.{              *end = *(end - 1);end--;}//更新*pos = x;_finish++;}

在实践中,我们发现这段代码并无法完成insert的功能,这是因为我们扩容是可能是异地扩容的,而pos的位置却没有变化。因此这时便产生了问题。

我们可以通过记录pos和start的距离并在扩容后更新从而实现该功能。

		void insert(iterator pos, const T& x){if (_finish == _end_of_storage)//判断{  //扩容size_t len = pos - _start;size_t newcapacity = capacity() == 0:4 ? 2 * capacity();reserve(newcapacity);pos = _start + len;}//后移iterator end = _finish;while (end>=pos+1)//移动本身则需要+1,不需要则不用+1.{              *end = *(end - 1);end--;}//更新*pos = x;_finish++;}

6.4erase

删除一个位置的数据,那么我们直接让后面的数据往前覆盖即可。

		iterator erase(iterator pos){assert(!empty() && (pos > _start && pos << _finish));iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;it++;}//更新_finish--;return pos;}

6.5swap

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);}

七.访问容器相关函数

我们直接访问即可。

		T& operator[](size_t i){return _start[i];}const T& operator[](size_t i) const{return _start[i];}

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

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

相关文章

百数移动端重大更新:全面优化,用户体验再升级!

本次发布的优化更新的功能&#xff0c;主要是为了提升用户的移动端使用体验。此次改版不仅优化了控件样式&#xff0c;提升视觉与交互体验&#xff0c;还在子表单功能上实现了重大突破&#xff0c;如新增复制、插入行功能等。 同时&#xff0c;新增功能——数据加载&#xff0…

Python之简单了解pylab绘图工具和汇编语言

《Python入门经典以解决计算问题为导向的Python编程实践》89-93页的笔记。 用pylab对数据绘图最小的通用计算 用pylab对数据绘图 PyLab是Matplotlib面向对象绘图库的过程界面。Matplotlib是整个软件包&#xff1b; matplotlib.pyplot是Matplotlib中的一个模块&#xff1b;而P…

【物联网】(蓝牙篇)微信小程序ios如何自动打开蓝牙

微信小程序打开蓝牙的便捷之道——微信小程序ios如何自动打开蓝牙 随着智能手机蓝牙技术和物联网产品的普及&#xff0c;很多人在使用微信小程序时&#xff0c;都希望能够更便捷地打开蓝牙功能。 在iOS系统上&#xff0c;由于其封闭性和权限控制严格&#xff0c;使得自动打开蓝…

扩散模型理论与公式推导——详细过程速览与理解加深

参考&#xff1a; [1] Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models[J]. Advances in neural information processing systems, 2020, 33: 6840-6851. [2] 扩散模型/Diffusion Model原理讲解_哔哩哔哩_bilibili [3] 扩散模型公式推导_扩散模型数学推导-C…

10、java程序流程控制之二:分支语句(switch-case结构)、循环结构(for循环)(经典案例)

java程序流程控制之二&#xff1a; Ⅰ、分支语句&#xff1a;switch-case1、switch-case 分支结构&#xff1a;其一、描述&#xff1a;其二、代码为&#xff1a;其三、截图为&#xff1a; 2、switch-case 分支结构的案例1&#xff1a;判断是否合格其一、描述&#xff1a;其二、…

Docker数据管理和网络管理

文章目录 一、Docker数据管理Docker容器的分层哪些数据需要持久化容器数据持久保存方式数据卷&#xff08;data volume&#xff09;数据卷的使用场景数据卷的特点数据卷使用方法实际例子 二、网络管理Docker安装完成后默认的网络设置创建容器后的网络配置修改默认网络设置容器名…

“南禾”女士包网站的设计与实现----附源码17160

摘 要 随着社会经济的发展和人们生活水平的提高&#xff0c;女士包市场逐渐成为一个庞大的产业。女性消费者对于时尚、品质和个性化的追求&#xff0c;对于高品质、款式新颖的女士包需求不断增加。南禾作为中高端女士包品牌&#xff0c;为抓住了这一市场需求&#xff0c;为女性…

再见,Midjourney | FLUX 彻底改变了 AI 图像游戏

Flux 刚发布一周&#xff0c;大家都疯了&#xff01; 因为真的是分不清是AI还是真实啊&#x1f3f4;‍☠️ Flux生成 Flux生成 FLUX 彻底改变了 AI 图像游戏。 02 黑深林 Black Forest Labs由Stable Diffusion模型的原班人马创立&#xff0c;旨在开发并开源高质量的图像和…

AI技术加速落地 港科广联手思谋打开智能缺陷检测新纪元

AI 技术应用落地的元年&#xff0c;工业是主战场&#xff0c;尤其是工业缺陷检测。 在“生产制造-缺陷检测-工艺优化-生产制造”的智能制造闭环链条中&#xff0c;基于AI的智能缺陷检测扮演着“把关者”的角色。但这个把关者长期以来却缺少一个称手的工具——样本量大、精度高…

【Cadence22】将别人发的原理图和PCB库修改为自己的库,进而继续制图

【转载】Cadence Design Entry HDL 使用教程 【Cadence01】Cadence PCB Edit相对延迟与绝对延迟的显示问题 【Cadence02】Allegro引脚焊盘Pin设置为透明 【Cadence03】cadence不小心删掉钢网层怎么办&#xff1f; 【Cadence04】一般情况下Allegro PCB设计时的约束规则设置&a…

Firefox滚动条在Win10和Win11下表现不一致问题?

文章目录 前言总结解决方法 前言 最近在写页面的时候发现一个非常有意思的事。Firefox滚动条在Win10和Win11下表现居然不一致。在网上几经查找资料&#xff0c; 终于找到原因所在。总结成下面的文章&#xff0c;加深印象也防止下次遇到。 总结 参考文章&#xff1a; Firefox…

云快充协议1.5版本的充电桩系统软件

介绍 小程序端&#xff1a;城市切换、附近电站、电桩详情页、扫码充电、充电中动态展示、订单支付、个人中心、会员充值、充值赠送、联系客服&#xff1b; 管理后台&#xff1a;充电数据看板、会员管理、订单管理、充值管理、场站运营、文章管理、财务管理、意见反馈、管理员管…

VBA学习(25):从一个工作表复制数据到另一个工作表

今天演示一个简单的例子&#xff0c;也是经常看到网友问的问题&#xff0c;将一个工作表中的数据复制到另一个工作表。 如下图1所示&#xff0c;有3个工作表&#xff0c;需要将工作表“新数据#1”和“新数据#2”中的数据复制到工作表“汇总”中。其中&#xff0c;在“汇总”工…

【Web】LIT CTF 2024 题解(全)

目录 anti-inspect jwt-1 jwt-2 traversed kirbytime scrainbow anti-inspect 因为一直while true&#xff0c;网页会卡死无法访问 const flag "LITCTF{your_%cfOund_teh_fIg_94932}";console.log(flag,"background-color: darkblue; color: white; f…

Python办公自动化:使用`xlutils` 修改Excel文档

在日常办公自动化中&#xff0c;除了读取Excel文件&#xff0c;我们还经常需要对文件进行修改或更新。在Python中&#xff0c;除了xlrd&#xff0c;还可以使用xlutils库来实现对Excel文件的修改操作。本文将继续以“巴黎奥运会奖牌榜.xlsx”文件为例&#xff0c;讲解如何使用xl…

eNSP 华为浮动路由

R1&#xff1a; <Huawei>system-view [Huawei]sysname R1 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 172.16.1.1 24 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip add 10.10.1.1 24 [R1-GigabitEthernet0/0/1]quit [R1]vlan 10 //e口是…

配置Google API,用JavaScript和Python读取Google sheet里的数据

[发布时间是2024年8月。技术流程可能会存在一些变动] 开头提醒一下各位公司&#xff0c;国内包括腾讯云华为云阿里云&#xff0c;国外包括AWS和GCP&#xff0c;希望你们在开发产品的同时把这方面的文档写的更清楚明白一些。 现在&#xff0c;随着办公逐渐云化&#xff0c;从云…

Linux学习笔记11(计算机网络)

目录 网络七层模型/五层模型 IP地址分类 CIDR Centos7的网卡IP配置 RockyLinux9的网卡IP配置 网络七层模型/五层模型 自下到上 物理层&#xff1a; 建立物理连接&#xff0c;传输 0 和 1 的比特流 数据链路层&#xff1a; 物理地址寻址&#xff0c;流量控制&#xff0c;差错…

【redis】springboot 用redis stream实现MQ消息队列 考虑异常ack重试场景

redis stream是redis5引入的特性&#xff0c;一定程度上借鉴了kafka等MQ的设计&#xff0c;部署的redis版本必须 > 5 本文主要讲的是思路&#xff0c;结合简单的源码分析&#xff08;放心&#xff0c;无需深入大量源码&#xff09;&#xff1b;讲述在redis stream文档缺乏&a…

链表的奇偶节点重新排列及空指针问题分析【链表、空指针】

在处理链表问题时&#xff0c;重组链表节点是一种常见需求。本文将详细探讨如何在链表中将奇数索引节点放在偶数索引节点之前&#xff0c;并深入分析实现过程中的空指针问题及其解决方案。 1. 问题描述 给定一个单链表&#xff0c;要求将链表中的节点按照奇数索引节点在前、偶…