C++ vector类的模拟实现

点击蓝字

b780f3c92f9d701f15239a4daf88c937.png

关注我们

1.前言

vector和string虽然底层都是通过顺序表来实现的,但是他们利用顺序表的方式不同,string是指定好了类型,通过使用顺序表来存储并对数据进行操作,而vector是利用了C++中的泛型模板,可以存储任何类型的数据,并且在vector中,并没有什么有效字符和容量大小的说法,底层都是通过迭代器进行操作的,迭代器底层实现也就是指针,所以说,vector是利用指针对任何顺序表进行操作的。

a5047d04b125a30e59350968c1886a0f.png

2.vector属性

  • _start用于指向第一个有效元素

  • _finish用于指向最后一个有效元素的下一个位置

  • _endOfStorage用于指向已经开辟了的空间的最后一个位置的下一个位置

  • vector的迭代器是原生态T*迭代器

template<class T>
class Vector
{
public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endOfStorage;
};

3.构造函数

无参默认构造函数,将所有属性都置空

以n个val初始化的构造函数,先开辟n个空间,再将这些空间的值都置为val,并更新_finish和_endOfStorage的位置

通过迭代器传参初始化的构造函数,使用新的迭代器,通过尾插将数据插入到新的空间

使用新的迭代器的原因是使传入的迭代器可以是任意类型的,如果使用Vector的迭代器,那么传入的迭代器的类型只能和Vector的类型一样,这里拿string举例,创建一个char类型的Vector,Vector,但是传入的迭代器并不是char类型的,可以是字符数组的迭代器或者是string的迭代器。只要通过解引用是char类型就可以

//无参默认构造Vector():_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){}//n个val的构造函数Vector(int n, const T& val = T()):_start(new T[n]),_finish(_start +n),_endOfStorage(_finish){for (int i = 0; i < n; ++i){_start[i] = val;}}//通过迭代器产生的构造函数template<class InputIterator>Vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endOfStorage(nullptr){while (first != last){pushBack(*first);++first;}}

运行结果在begin() 和end()实现中

4.size()和capacity()

指针相减得到的值就是这两个指针之间的元素个数

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

9e945925251310a819fc2227a95224c0.png

5.pushBack()

  • 检查容量,如果_finish和_endOfStorage指针相等,说明容量已经满了,需要开辟更大的空间

  • 在_finish位置插入新的数据

  • 更新_finish

void pushBack(const T& val)
{//检查容量if (_finish == _endOfStorage){size_t newC = _endOfStorage == nullptr ? 1 : 2 * capacity();reserve(newC);}//插入数据*_finish = val;//更新finish++_finish}

运行结果在begin() 和end()实现中

6.reserve

  • 检查n的合理性,reserve只能扩大不能缩小空间

  • 保存有效元素的个数,用于后面更新_finish使用

  • 申请空间并将数据拷贝到新的空间中,释放旧的空

  • 更新3个成员变量,注意_finish不能更新为_finish+size(),原因是size()是通过两指针运算得出来的,此时的_fiinsh已经指向了释放的空间,再去使用会出错,所以这也是有第二步的原因

以下代码存在浅拷贝问题,文章末尾会给出正确深拷贝代码和详细解释

void reserve(size_t n)
{//reserve只能扩大空间不能缩小空间if (n > capacity()){//保存有效元素size_t sz = size();//申请空间T* tmp = new T[n];//将数据拷贝到新的空间if (_start != nullptr){//拷贝有效元素memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}//更新_start = tmp;_finish = _start + sz;_endOfStorage = _start + n;}}

运行结果在begin() 和end()实现中

7.begin() 和end()

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

3683edffe5cb9d83776cfb9dfb073d92.png有了begin()和end就可以使用范围for

template<class T>
void printVectorFor(Vector<T>& vec)
{for (auto& e : vec){cout << e;}cout << endl;
}

d5adb7f35950c659d08e10c6c5a6096b.png

8.[]运算符重载

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

94b2231a71f6df3dc165240fe7e4d029.png

9.resize()

  • n <= size 直接更新_finish的位置即可

  • size < n <= capacity,从_finish开始补充元素,补充到_start+n的位置,然后执行第一步

  • n > capacity 增容,执行第二和第一步

void resize(size_t n, const T& val = T())
{//3.n >= capacityif (n > capacity()){reserve(n);}//2.size < n <= capacityif (n > size()){while (_finish != _start + n){*_finish = val;++_finish;}}//1.n<=size_finish = _start + n;}

d2d9aed3fbb4b1a8e9cc556f134988ee.png

10.insert()

  • 检查插入的位置的有效性[_start, _finish)

  • 检查容量,由于增容会导致pos迭代器失效,所以我们可以先保存pos对于_start的偏移量offset,增容后,再将pos重新赋值pos=_start+offset

  • 移动元素,从后往前移动,最后将pos位置的元素置为val

  • 更新_finish

void insert(iterator pos, const T& val)
{//检查位置有效性assert(pos >= _start || pos < _finish);//检查容量if (_finish == _endOfStorage){//增容会导致迭代器失效//保存pos和_start的偏移量size_t offset = pos - _start;size_t newC = _endOfStorage == nullptr ? 1 : 2 * capacity();reserve(newC);//更新pospos = _start + offset;}//移动元素iterator end = _finish;while (end != pos){*end = *(end - 1);--end;}//插入*pos = val;//更新++_finish;}

e178710fd5e33bb9eb129e871f44b322.png

11.erase()

  • 检查位置有效性

  • 移动元素,从前向后移动

  • 更新_finish

iterator erase(iterator pos)
{//检查位置有效性assert(pos >= _start || pos < _finish);//移动元素,从前往后iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}//更新--_finish;}

5d5fea009e5acc3aa1ce7e10ba4d2dac.png

12.void popBack()

利用erase接口进行尾删

void popBack(){if (size() > 0)erase(end() - 1);}

fccb7181889b1d9e0f5d0d20e627a593.png

13.析构函数

~Vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}

14.算法库中的find

头文件<algorithm>

template <class InputIterator, class T>InputIterator find (InputIterator first, InputIterator last, const T& val)

参数内容(从迭代器的begin起到end中,找到val值,找到返回该值所在的迭代器,找不到返回end)

83e350ddad37d3bdf79c6796d86ee53e.png

15.reserve的深浅拷贝问题

当我门使用自定义类型时,使用浅拷贝是效率最高的,但是当我们使用自定义类型时,并且存在内存资源的利用,就必须时刻注意存在的深浅拷贝问题。来看以下代码测试

void test()
{Vector<string> v;string str1 = "123";string str2 = "456";string str3 = "789";v.pushBack(str1);v.pushBack(str2);v.pushBack(str3);
}

调试结果:

3f251dea12cf8e62e53371aa631143b4.png

当我们在插入第三个字符串时,就发生了内存异常的问题,我们来看看到底是什么问题。
第一次插入str1,没有问题

f3a7e57fb423973e2fa80abc1389aa62.png

第二次插入str2,插入之前我们会扩容,会创建2倍大的空间tmp,然后通过memcpy内存拷贝(浅拷贝)将内容拷贝到tmp中,此时就有两个指向指向一个资源(123),拷贝完后delete[]要删除原有空间,将123释放后,其实现在新的空间的第一个元素指向的是一个已经释放了的空间,但是问题并没有暴露出来,第二个元素的插入也没有问题

88c7e1e51d02207c2cd04601a3eb963b.png

第三次str3的插入,这次插入也会进行扩容,会先开辟一个2倍大的空间tmp,然后通过memcpy内存拷贝(浅拷贝)将内容拷贝到tmp中,此时有两个指针指向已经释放的资源(123),有两个指针指向资源(456),当拷贝完成后会释放旧的空间,当释放原指针指向的(456)时不会报错,原因和第二次插入原因一样。但是释放原有空的第一个指针时,就会发生内存报错异常,原因是资源(123)已经被释放了,如果再释放就属于二次释放,是不安全的。内存错误就报异常。

e177381fdd87203078c267d186dbc58e.png

所以我们在扩容的时候不应该只是单纯的浅拷贝,也就是使用memcpy来拷贝内容,我们应该要使用深拷贝。将memcpy改为for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];}
整体代码如下:

void reserve(size_t n)
{//reserve只能扩大空间不能缩小空间if (n > capacity()){//保存有效元素size_t sz = size();//申请空间T* tmp = new T[n];//将数据拷贝到新的空间if (_start != nullptr){//拷贝有效元素//memcpy(tmp, _start, sizeof(T) * size());//深拷贝for (size_t i = 0; i < sz; ++i){//调用自定义类型的赋值运算符重载函数,完成深拷贝//前提是该重载函数也是深拷贝,string是STL库中,是被深拷贝处理过tmp[i] = _start[i];}delete[] _start;}//更新_start = tmp;_finish = _start + sz;_endOfStorage = _start + n;}}

44bae889314dfec245e20c6dd76add32.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

e68d7763c649cf1322dbd1686f703fd3.gif

戳“阅读原文”我们一起进步

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

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

相关文章

visual studio源文件的编译顺序是依据什么?

问题&#xff1a;visual studio源文件的编译顺序是依据什么&#xff1f; 结论&#xff1a;依据 .vcxproj 文件里 指定了ClCompile的ItemGroup &#xff0c;如下图所示&#xff0c;就是这么简单粗暴。

功能齐全的屏幕截图C++实现详解

点击蓝字关注我们1、概述要使用屏幕截图&#xff0c;其实很容易&#xff0c;装一款聊天软件或者办公软件就可以了&#xff0c;比如QQ、企业微信、钉钉、飞书等。但要开发出类似这些软件的屏幕截图模块&#xff0c;则没那么容易。其实实现屏幕截图的技术并不复杂&#xff0c;主要…

如何判断exe文件是debug还是release编译生成的

如何判断exe文件是debug还是release编译生成的结论&#xff1a; 用IDA工具打开exe&#xff0c;然后看Imports里面的依赖库是否有带d或D结尾的&#xff0c;如果有就说明是Debug的 实验&#xff1a;&#xff08;实验环境 vs2017&#xff0c; IDA工具&#xff09; &#xff08;0&…

大屏可视化分配率是什么意思_什么是分配率?

大屏可视化分配率是什么意思诸如“不可持续的分配率”和“您需要保持较低的分配率”之类的短语似乎仅属于Java Champions的词汇表。 复杂&#xff0c;恐怖并被魔术光环包围。 经常发生的情况是&#xff0c;当您更仔细地查看概念时&#xff0c;魔术会随着抽烟消失。 这篇文章试…

C/C++语言动态开辟的杨辉三角

点击蓝字关注我们问题引入杨辉三角相必大家并不陌生&#xff0c;第1行有1列、第二行有2列…第n行有n列&#xff0c;且每行行首和行尾的值都为1&#xff0c;其余的值为上一行两数相加我们在C语言阶段&#xff0c;第一次碰到的杨辉三角应该都是用常规的二维数组存储&#xff0c;可…

git gui 历史版本_这些Git命令都不会,还是不要去面试了

前言以下&#xff0c;项目中经常使用的Git命令&#xff0c;汇总到这里以便与你能快速的学习和掌握Git命令&#xff0c;在文章最后有惊喜哟&#xff0c;一定要看到最后啊&#xff01;使用的 Git版本&#xff1a;git version 2.24.0命令git log# 输出概要日志,这条命令等同于# gi…

java restful_Java EE中的RESTful计时器

java restful在这篇文章中...。 EJB计时器旋风之旅 通过带有示例实现的简单REST接口即时使用EJB计时器 更新&#xff08;2015年7月14日&#xff09; 该应用程序的前端现在可以在OpenShift上使用 。 由于我是前端新手&#xff0c;因此我在其他来源的帮助下组装了此HTML5 Ang…

c# 联合halcon 基于相关性 模板匹配_机器视觉之halcon入门(5)-字符识别exe生成...

2.3.2 第二个halcon程序转EXE程序&#xff1a;字符识别老规矩&#xff0c;每一段halcon代码得用C#二次开发下。根据上一节所教的&#xff0c;我们配置下C#的环境&#xff0c;顺便添加好控件&#xff0c;如下图(2-3-2-1)。图 2-3-2-1控件基本跟上一节一样&#xff0c;只是少了一…

C语言数据的存储和取出(超详细讲解)

点击蓝字关注我们整形的储存我们知道一个整形的存储是以补码的形式储存取出是原码的形式。比如&#xff1a;int a 5;的二进制是101那它的原码应该是&#xff1a;00000000 00000000 00000000 00000101正数的原反补相同那它存进去和取出来都是&#xff1a;00000000 00000000 000…

打印pdf就一页_PDF 文件转换工具

是将 PDF 文件转换为完全可编辑的 Windows 文档最好的转换软件。无论您需要您的内容是 Microsoft Word、Excel、PowerPoint、HTML 还是仅需要文本&#xff0c; 总会给您一个简单的方法&#xff0c;快捷地获取您要的内容。可转换整个文档或选择内容。亦可创建 PDF 文件。PDF 转换…

C++类的this指针,静态成员,友元函数友元类

点击蓝字关注我们1. this指针在上篇讲C中类&#xff0c;对象&#xff0c;封装&#xff0c;继承&#xff08;派生&#xff09;&#xff0c;多态的时候&#xff0c;this指针出现在成员函数中&#xff0c;并使用->成员提取符操作成员变量。在 C 中&#xff0c;每一个对象都能通…

批量提取文件创建时间_批量采集新浪微博用户内容

有时我们需要把某些用户的微博数据全部采集下来用作分析&#xff0c;每条信息复制的工作量是非常低效的&#xff0c;必须要借助工具。今天给大家介绍一款采集软件&#xff1a;微风采集器。打开软件&#xff0c;选择模板&#xff0c;下拉框选&#xff1a;批量提取指定用户微博内…

C++异常的规则

点击蓝字关注我们异常是指存在于程序运行时的异常行为&#xff0c;这些行为超出了函数正常功能的范围&#xff0c;当程序的某部分检测到一个无法处理的问题时&#xff0c;就需要用到异常处理。1. C语言中传统的处理错误方式终止程序&#xff1a;如assert&#xff0c;当发生错误…

异质性查询需要为连线设定_振奋人心!华东理工大学开发新型的荧光染料,为细胞成像奠定基础...

结合并激活荧光染料的适体荧光RNA(FR)已用于对丰富的细胞RNA种类进行成像。然而&#xff0c;诸如低亮度和具有不同光谱特性的染料/适体组合的有限可用性的局限性&#xff0c;限制了这些工具在活的哺乳动物细胞和体内的使用。最近&#xff0c;华东理工大学朱麟勇及杨弋共同通讯在…

C++ STL详解(1)

点击蓝字关注我们概述STL 是“Standard Template Library”的缩写&#xff0c;中文译为“标准模板库”。STL 是 C 标准库的一部分&#xff0c;不用单独安装。C 对模板&#xff08;Template&#xff09;支持得很好&#xff0c;STL 就是借助模板把常用的数据结构及其算法都实现了…

各种说明方法的答题格式_高中化学:选择题答题方法与知识点总结,让你轻松秒杀各种难题...

选择题是化学考试中被广泛采用的一种题型。它具有知识容量大&#xff0c;覆盖面广&#xff0c;构思新颖、灵活巧妙&#xff0c;考试的客观性强&#xff0c;答题简单&#xff0c;评分容易、客观准确等优点。 选择题按考查形式可分为三种类型&#xff0c;即&#xff1a;常规型选择…

C++ STL详解(2)

点击蓝字关注我们来源自网络&#xff0c;侵删刷题时常用的STLstring之前写过一篇 string 的简介但是不是特别全面&#xff0c;这里再补充说明一下。size()返回字符串中字符的数量#include<iostream> #include<string>using namespace std;int main() {string str …

斐波那契数列的四种实现方式(C语言)

点击蓝字关注我们来源自网络&#xff0c;侵删斐波那契数列是一组第一位和第二位为1&#xff0c;从第三位开始&#xff0c;后一位是前两位和的一组递增数列&#xff0c;像这样的&#xff1a;0、1、1、2、3、5、8、13、21、34、55......今天&#xff0c;我们用四种方式来进行实现…

linux make命令_第一章 1.3Linux下安装Redis

1.3.2 Linux下安装Redis第一步: 去官网下载安装包 ,传送门第二步: 上传到Linux服务器,解压redis的安装包tar -zxvf redis-6.0.8.tar.gz这里我已经解压好了,并且移动到了redis目录下第三步: 安装基本环境yum -y insatll gcc-c这里注意一个问题,Centos下安装的gcc默认版本为4.8.5…

C语言strcpy函数的使用

点击蓝字关注我们strcpy简单使用&#xff1a; #include <stdio.h> #include <string.h>struct Student {int sid;char name[20];int age;} st; //此处分号不可省略int main(void) {struct Student st {1000,"zhangsan",20};printf("%d %s %d\n&…