探索C++中的动态数组:实现自己的Vector容器

请添加图片描述

🎉个人名片

🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉
————————————————

🎉文章简介

🎉本篇文章将 介绍如何使用C++编写代码来实现一个类似于STL中的Vector容器 等学习的相关知识进行分享!

💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉
——————————————————————————

一.前言

这篇文章将介绍如何使用C++编写代码来实现一个类似于STL中的Vector容器。Vector是一种动态数组,它可以根据需要自动调整大小,并提供了许多方便的方法来操作数据。在这篇文章中,你将学习如何使用指针和动态内存分配来创建一个可变大小的数组,并实现Vector的常见功能,如添加元素、删除元素、访问元素等。通过实现自己的Vector容器,你将更好地理解动态数组的原理和实现方式,并提升对C++语言的理解和掌握。

二.Vs下Vector的底层结构

vs下底层是一个类,类里面的成员变量包括三个指针,指针类型为所存储数据类型(T)的指针;
T* _start 指向的是存储数据所开空间的起始位置;
T* _finish 指向的是最后一个数据的下一个位置;
T* _endofstorage 指向的是所开空间的最后的下一个位置;

如图:
在这里插入图片描述

public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start;iterator _finish;iterator _endofstorage;

三.vector的模拟实现

1.构造函数

1.直接初始化为空指针,使用时再开空间

vector():_start(nullptr),_finish(nullptr)         //也可以在定义的时候直接给缺省值,_endofstorage(nullptr)
{}

2.用一个迭代器区间构造(需要复用下面实现的push_back函数)

	template<class intputiterator>     //模板vector(intputiterator first, intputiterator last){while (first != last)  //不能是用<判断,因为底层不一定连续{push_back(*first);    //依次取里面的数据尾插++first;}}

3.用n个T类型构造对象(这里需要后面实现的resize函数)

vector(size_t n,const T& x = T())
{resize(n,x);
}
vector(int n, const T& x = T())
{resize(n,x);
}

注意:这里为什么要实现两个?
在这里插入图片描述

2.reserve函数

结合下面代码和图解看看思路解析:
1.reserve函数可以单独使用,也可以在其他接口种会使用,我们实现的该函数不会缩容,所以最开始会加一个判断是否缩容;
2.reserve函数的实现是开一个新空间,将原空间的数据拷贝到新空间,再对_start,_finish,_endofstorage 进行处理;
3.这里需要记录一个原空间存储的有效数据的个数,为了确定_finish的位置(如果不存储,则当开辟好了新空间后,_finish的位置不能确定)
图解:
在这里插入图片描述

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();      //旧空间有效数据个数size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();   //需要判断,因为可能为0iterator tmp = new T[newcapacity];     //开空间//memcpy(tmp, _start,old_size * sizeof(T));   //下面会将为什么不用memmove函数for (int i = 0; i < old_size; i++){tmp[i] = _start[i];       //拷贝数据}delete[] _start;             //释放旧空间_start = tmp;_finish = _start + old_size;      //确定_finish的位置_endofstorage = _start + newcapacity;     }
}

为什么不用memmove?
当我们存储的数据类型为string时【如图一】每个string对象里面都有一个_str,指向一个字符串,当我们用memmove拷贝后,拷贝后的_str与拷贝前的_str指向同一块空间【如图二】,当我们释放_start的时候,会调用string的析构函数,将该空间释放掉,就会导致野指针问题;

在这里插入图片描述

3.push_back函数

思路:检查是否空间满了,扩容,直接尾插

	void push_back(const T& x){if (_finish == _endofstorage)    //检查是否需要扩容{reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;     //尾插++_finish;    //更新下标}

4.push_back函数

思路:与顺序表实现一样,直接–_finish;

void pop_back()
{assert(size());   //检查是否还有有效数据可删--_finish;
}

5.resize函数

思路:
分为三种可能:
1.n>capacity 扩容+尾插
2.size<n<capacity 直接尾插
3.n<size 尾删

值得注意的是传参给了缺省值,因为存储数据可能是string这种数据,给了缺省值会去掉对应的默认构造函数,虽然内置类型没有默认构造,但是为了解决这类问题,有了类似于默认构造类处理内置类型;

void resize(size_t n,T x=T() )   {if (n<size())     //直接改变_finish{_finish = _start + n;;}else{if (n > capacity())   //扩容{reserve(n);}for (size_t i = size(); i <= n; i++)  //尾插{_start[i] = x;      //可以复用push_back函数}_finish = _start + n;    //更新}}

6.insert函数

思路:
1.检查是否需要扩容
2.挪动数据
3.插入,更新下标

iterator insert(iterator pos, const T& x)
{assert(pos);assert(pos >= _start);     //断言assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;              //记录之前的值reserve(capacity() == 0 ? 4 : 2 * capacity();    //扩容pos = _start + len;          //更新pos下标}//memmove(pos+1, pos, sizeof(T) * (_finish - pos));  iterator end = _finish-1;while (end>=pos){*(end+1) = *end;     //拷贝end--;}*pos = x;     //插入++_finish;    return pos;    //返回pos位置的迭代器,防止迭代器失效问题
}

7.erase函数

直接挪动数据覆盖

	iterator erase(iterator pos){assert(pos < _finish && pos >= _start);   //断言iterator end = pos;while (end < _finish){*end = *(end + 1);   //挪动数据覆盖end++;}--_finish;          //更新下标return pos;      //返回pos位置的迭代器,防止迭代器失效问题}

8.swap函数

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

9.赋值运算符重载

与上章《魔法之线:探索string类的神秘世界》链接: link赋值运算符重载方法一样;

现代写法:

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

10.拷贝构造函数

传统写法:

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

稍便捷的方式
直接复用尾插函数,将对象v的数据一个一个尾插;

		vector(vector<T>& v):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){reserve(v.capacity());      //提前空间,减少尾插扩容for (const auto& e : v){push_back(e);}}

11.其他简单函数的实现:

		//判空bool empty(){return size();}//返回第一个数据T& front()const{return *_start;}//返回最后一个数据T& back()const{return *(_finish-1);}//返回有效数据个数size_t size()const{return _finish - _start;}//返回容量size_t capacity()const{return _endofstorage - _start;}//[]运算符重载T& operator[](size_t pos){assert(pos>=0 && pos<size());return _start[pos];}T& operator[](size_t pos)const{assert(pos >= 0 && pos < size());return _start[pos];}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}~vector(){if(_start)delete[] _start;_start = _finish = _endofstorage = nullptr;}

💕最后希望内容对大家有所帮助😊😊😊

请添加图片描述

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

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

相关文章

mybatis基础操作(三)

动态sql 通过动态sql实现多条件查询&#xff0c;这里以查询为例&#xff0c;实现动态sql的书写。 创建members表 创建表并插入数据&#xff1a; create table members (member_id int (11),member_nick varchar (60),member_gender char (15),member_age int (11),member_c…

什么时候去检测大数据信用风险比较合适?

什么时候去检测大数据信用风险比较合适?在当今这个数据驱动的时代&#xff0c;大数据信用风险检测已经成为个人的一项重要需求。本文将从贷前检测、信息泄露检测和定期检测三个方面&#xff0c;阐述何时进行大数据信用风险检测较为合适。 一、贷前检测 大数据信用风险检测在贷…

指针【理论知识速成】(3)

一.指针的使用和传值调用&#xff1a; 在了解指针的传址调用前&#xff0c;先来额外了解一下 “传值调用” 1.传值调用&#xff1a; 对于来看这个帖子的你相信代码展示胜过千言万语 #include <stdio.h> #include<assert.h> int convert(int a, int b) {int c 0…

【Python】python实现Apriori算法和FP-growth算法(附源代码)

使用一种你熟悉的程序设计语言&#xff0c;实现&#xff08;1&#xff09;Apriori算法和&#xff08;2&#xff09;FP-growth算法。 目录 1、Apriori算法2、F-Growth算法3、两种算法比较 1、Apriori算法 def item(dataset): # 求第一次扫描数据库后的 候选集&#xff0c;&am…

OpenCASCADE开发指南<四>:OCC 数据类型和句柄

一个软件首先要规定能处理的数据类型&#xff0c; 其次要实现三项最基本的功能——引用管理、内存管理和异常管理。在 OCC 中&#xff0c;这三项功能分别对应基础类中的句柄、内存管理器和异常类。 1 数据类型 在基本概念篇里&#xff0c;已经介绍了 OCC 数据类型的分类&…

Linux:好用的Linux指令

进程的Linux指令 1.查看进程信息 ​​​​ps ajx | head -1 && ps ajx | grep 进程名创建一个进程后输入上述代码&#xff0c;会打印进程信息&#xff0c;当我们在code.exe中写入打印pid&#xff0c;ppid&#xff0c;这里也和进程信息一致。 while :; do ps ajx | he…

Python语言在编程业界的地位——《跟老吕学Python编程》附录资料

Python语言在编程业界的地位——《跟老吕学Python编程》附录资料 ⭐️Python语言在编程业界的地位2024年3月编程语言排行榜&#xff08;TIOBE前十&#xff09; ⭐️Python开发语言开发环境介绍1.**IDLE**2.⭐️PyCharm3.**Anaconda**4.**Jupyter Notebook**5.**Sublime Text** …

机器学习——过拟合问题、正则化解决法

过拟合的基本概念 欠拟合&#xff1a;假设函数没有很好的拟合训练集数据&#xff0c;也称这个假设函数有高偏差&#xff1b; 过拟合&#xff1a;过拟合也称为高方差。在假设函数中添加高阶多项式&#xff0c;让假设函数几乎能完美的拟合每个样本数据点&#xff0c;这看起来很…

使用ubuntu搭建hadoop伪分布全过程图解

目录 1. 安装jdk 2. 添加java环境变量 3. 设置免密登录 4. 安装hadoop 5. 添加hadoop环境变量 6. 修改hdoop-env.sh文件 7. 修改core-site.xml文件 8. 修改yarn-site.xml文件 9. 修改mapred-site.xml文件 10. 修改hdfs-site.xml文件 11. 进行授权 12. 创建namenode…

【论文精读】Transformer:Attention Is All You Need

《动手学深度学习》关于Transformer和注意力机制的笔记 李沐《动手学深度学习》注意力机制 文章目录 《动手学深度学习》关于Transformer和注意力机制的笔记一、文章概览&#xff08;一&#xff09;摘要&#xff08;二&#xff09;结论部分&#xff08;三&#xff09;引言&am…

java基础2-常用API

常用API Math类 帮助我们进行数学计算的工具类。 里面的方法都是静态的。 3.常见方法如下&#xff1a; abs:获取绝对值 absExact:获取绝对值 ceil:向上取整 floor:向下取整 round:四舍五入 max:获取最大值 …

第十四届蓝桥杯蜗牛

蜗牛 线性dp 目录 蜗牛 线性dp 先求到达竹竿底部的状态转移方程 求蜗牛到达第i根竹竿的传送门入口的最短时间​编辑 题目链接&#xff1a;蓝桥杯2023年第十四届省赛真题-蜗牛 - C语言网 关键在于建立数组将竹竿上的每个状态量表示出来&#xff0c;并分析出状态转移方程 in…