【c++】vector模拟

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕vector模拟

> 毒鸡汤:在等待的日子里,刻苦读书,谦卑做人,养得深根,日后才能枝叶茂盛。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

        我们已经了解了vector的知识点,学完知识点必然需要来手搓一个vector,这样对知识点掌握更加熟练,如果大家写过string模拟的话,手撕vector就更加容易些😚😚。那咱们话不多说进入今天的主题---->【c++】vector模拟。

 ⭐主体

这里我们就不分解成三文件啦,这里就创建两个文件vector.h(头文件),test.c(测试代码文件)

而我们今天就不像string模拟一样啦,咱们按照下面的方式来模拟vector。

🌙基本框架结构

为了避免和库里的vector产生冲突,在自己的命名空间内实现vector

namespace lyk
{template<class T>//通过模板能够实现不同类型的数据存储class vector{public:typedef	T* iterator;/*各类函数功能实现*/private:iterator _start;          //指向容器中的起始位置iterator _finish;         //指向容器中最后一个有效数据的下一个位置iterator _end_of_storage; //指向容器中现有容量的位置};
}

这里我们需要简单了解成员变量的作用,以下面的图解来解析作用:



🌙默认成员函数

这个板块中我们得讲解vector的初始化,销毁.....

💫构造函数

在构造函数中无非就是初始化列表,简单来讲是实现初始化。

💦无参构造函数

无参的构造函数,把三个成员变量设置为空指针。

//构造函数 --- 无参构造
vector()//初始化成员列表:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{}
💦有参构造函数1

迭代器区间构造函数,由于不确定元素类型我们就用模板来解析元素类型,之后再尾插。

//构造函数2
template<class InputIterator> //模板函数
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{//将迭代器区间在[first,last)的数据一个个尾插到容器当中while (first != last){push_back(*first);first++;}
}

实例:

int a[5] = { 1,2,3,4,5 };
vector<int>v1(a, a + 5);

💦有参构造函数2

该容器当中含有n个值为val的数据。将容器容量先设置为n,尾插n个值为val的数据。

//构造函数3
vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n); //调用reserve函数将容器容量设置为nfor (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中{push_back(val);}
}

💫析构函数

在析构函数中无非就是销毁数据,简单来讲释放内存。

~vector() // 析构函数(销毁)
{if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}
}

💫拷贝构造

对于拷贝构造函数,由于涉及到深浅拷贝的问题,我们这里提供传统写法与现代写法。

💦传统写法

咱们先看看思路:

  1. 先开辟一块与该容器大小相同的空间。
  2. 然后将该容器当中的数据一个个拷贝过来即可。
  3. 最后更新_finish和_endofstorage的值即可。

1.

//传统写法2.
//出现深浅拷贝问题
//v2(v1) 
vector(const vector<T>& v)  // 拷贝构造
{_start = new T[v.capacity()];memcpy(_start, v._start, v.size()* sizeof(T));_finish = _start + v.size();_endofstorage = _start + v.capacity();
}

2.

//传统写法2.
//解决深浅拷贝问题
vector(const vector<T>& v)
{_start = new T[v.capacity()]; //让v2开辟一块和v1一样大小的空间for (size_t i = 0; i < v.size(); i++){_start[i] = v[i];//通过循环进行赋值}//最后调整_finish和_end_of_storage的大小_finish = _start + v.size();_end_of_storage = _start + v.capacity();}

在这里就会出现一个问题:出现深浅拷贝问题。

  • 写法1对于数据的拷贝采用的是memcpy函数。
  • 写法2对于数据的拷贝采用的是for循环进行赋值拷贝。

两者在拷贝数据的方式上对于内置类型或不需要进行深拷贝的自定义类型,完全是满足要求的,但是当vector存储的是string时,一定存在问题。

采用图解:

存储了5个数据,每个数据都是string类,vector<string>v2(v1),v2也开辟了5个空间。

  • 写法1,在memcpy下完成拷贝,但是它们却指向了同一块空间,在调用析构函数时,就会导致同一块空间释放多次,最终内存泄露。
  • 写法2,它会去调用string类的赋值重载函数进行一个深拷贝。
💦现代写法
  • 使用范围for(或是其他遍历方式)对容器v进行遍历。
  • 在遍历过程中将容器v中存储的数据一个个尾插过来即可。
//现代写法
// v2(v1)
vector(const vector<T>& v) // 拷贝构造
{reserve(v.capacity());   // 判断是否需要扩容for (const auto& e : v)  {push_back(e);        // 拷贝尾插}
}

💫赋值运算符重载函数

 赋值运算符重载的进行是深拷贝,是将深拷贝出来的对象与左值进行了交换。

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

这里也有传统写法和现代写法,博主这里是现代写法,有兴趣的小伙伴们大家可以写写传统写法。

💫析构函数

首先判断该容器是否为空容器。

  • 若为空容器,则无需进行析构操作。
  • 若不为空,则先释放容器存储数据的空间。

然后将容器的各个成员变量设置为空指针即可。

代码实现:

~vector() // 析构函数(销毁)
{if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}
}

🌙迭代器相关的函数

begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址。

iterator begin()  // 返回容器的首地址
{return _start;
}iterator end()    // 返回容器当中有效数据的下一个数据的地址
{return _finish;
}

const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改。

const_iterator begin() const  // 返回容器的首地址
{return _start;
}const_iterator end() const  //返回容器当中有效数据的下一个数据的地址
{return _finish;
}

小试牛刀:

int main()
{vector<int> v{ 1,2,3,4,5 };//范围for进行遍历for (auto e : v){cout << e << " ";}cout << endl;return 0;
}

运行结果:

🌙容量相关的函数

💫size函数和capacity函数

咱们再看看这张图:

这两个指针之间对应类型的数据个数,所以size可以由_finish - _start得到,而capacity可以由_endofstorage - _start得到。

size_t size() const      // 返回容器当中有效数据的个数
{return _finish - _start;
}size_t capacity() const  // 返回当前容器的最大容量 
{return _endofstorage - _start;
}

💫reserve函数

reserve函数:

  • 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  • 当n小于对象当前的capacity时,不进行操作。
void reserve(size_t n)   // 判断扩容
{if (n > capacity()){size_t old = size();T* tmp = new T[n];if (_start){memcpy(tmp, _start, old * sizeof(T));delete[] _start;}_start = tmp;_finish = _start + old;_endofstorage = _start + n;}
}

首先得算好增容前的数据个数,因为增容完后,就需要释放旧空间。

1.如果没有对增容前的数据个数进行记录: 

2.如果增容前后的数据拷贝使用memcpy:

💫resize函数

resize规则:

  • 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  • 当n小于当前的size时,将size缩小到n。
// 判断缩括容
// T()是匿名对象,它的生命周期本来只是存在于当前行,
// 但是被const修饰以后,可以延长它的生命周期
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;}}
}

💫empty函数

通过比较容器当中的_start和_finish指针的指向,若所指位置相同且为空,则该容器为空。

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

🌙增删查改相关的函数

💫push_back函数

  1. 首先得判断容器是否已满。
  2. 若已满则需要先进行增容。
  3. 然后将数据尾插到_finish指向的位置。
  4. 再将_finish++。
void push_back(const T& x)  // 尾插
{if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;
}

💫pop_back函数

  1. 先判断容器是否为空
  2. 若为空则做断言处理
  3. 若不为空则将_finish--。
void pop_back() // 尾删
{assert(size() > 0);--_finish;
}

小试牛刀:

//测试一
void test_vector1()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;vector<int>::iterator it1 = v.begin();while (it1 != v.end()){cout << *it1 << " ";++it1;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;
}int main()
{test_vector1();return 0;
}

运行结果:

💫insert函数

insert函数可以在指定的pos位置插入数据。

  1. 在插入数据前先判断是否需要增容。
  2. 然后将pos位置及其之后的数据统一向后挪动一位。
  3. 最后将数据插入到pos位置。
void insert(iterator pos, const T& x) // 某个位置插入数据
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}memmove(pos + 1, pos, sizeof(T) * (_finish - pos));*pos = x;++_finish;
}

💫erase函数

erase函数可以删除所给迭代器pos位置的数据

  1. 在删除数据前需要判断容器释放为空。
  2. 若为空则需做断言处理。
  3. 删除数据时直接将pos位置之后的数据统一向前挪动一位。
  4. 将pos位置的数据覆盖即可。
iterator erase(iterator pos) // 删除所给迭代器pos位置的数据
{assert(pos >= _start);assert(pos < _finish);iterator begin = pos + 1;while (begin < _finish){*(begin - 1) = *begin;++begin;}--_finish;return pos;
}

💫swap函数

swap函数简单来说就是交换两个容器的数据。

void swap(vector<T>& v) // swap函数用于交换两个容器的数据
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}

💫print_vector函数

print_vector函数来打印我们容量的数据。

void print_vector(const vector<int>& v) // 打印
{for (auto e : v){cout << e << " ";}cout << endl;
}

🌙访问容器相关函数

这里有两个操作符重载函数:

  • 第一个可以读也可以修改。
  • 第二个只可以读不可以修改。
T& operator[](size_t i)
{assert(i < size()); //检测下标的合法性return _start[i]; //返回对应数据
}const T& operator[](size_t i)const
{assert(i < size()); //检测下标的合法性return _start[i]; //返回对应数据
}

 🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

MYSQL - SQL优化

插入数据优化 小批量数据 批量插入 最好插入500-1000条比较好 手动提交事务 主键顺序插入 大批量插入数据 主键优化 页分裂 页合并 主键优化设计原则 order by优化 group by优化 limit优化 count优化 count(1)里面不一定必须1&#xff0c;数字都可以 update优化 更新字…

安卓多用户管理之Userinfo

目录 前言Userinfo----用户信息1.1 属性1.2 构造器1.3 信息的判断及获取方法1.3.1 获取默认用户类型1.3.2 基础信息判断 1.4 序列化部分 总结 前言 UserManagerService内部类UserData中有一个Userinfo类型的info参数&#xff0c;在UserData中并未有所体现&#xff0c;但在后续…

基于长短期神经网络lstm的电子密度预测

目录 背影 摘要 代码和数据下载:基于长短期神经网络lstm的电子密度预测(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/abc991835105/88714821 LSTM的基本定义 LSTM实现的步骤 基于长短期神经网络lstm的电子密度预测 结果分析 展望 参考论文 背影 …

arch modelsim 解决无法运行

13.0的quartus modelsim版本10.1d 是32位的 修改/etc/pacman.conf [multilib] Include /etc/pacman.d/mirrorlistpacman -Sy安装 lib32-l…

《linux就该这样学》笔记(抽空更新)

2.3常用系统工作命令 2.3.1 echo 2.3.2 date 2.3.3 timedatectl 2.3.4 redoot 2.3.5 poweroff 2.3.6 wget 2.3.7 ps 2.3.8 pstree 2.3.9 top 2.3.10 nice 2.3.11 pidof 2.3.12 kill 2.3.13 killall 2.4系统状态检测命令 2.4.1 ifconfig 2.4.2 uname 2.4.3 upt…

OSPF基础

0x00 前言 本篇简述OSPF相关知识 0x01 正文 为什么需要动态路由协议 静态路由无法适应较大的网络无法动态的随着网络的变化而自动化&#xff0c;耗费人力 动态路由协议 什么是BGP协议 基于距离矢量算法修改后的算法形成协议&#xff0c;被称为路径矢量路由协议 BGP工作…

Spring MVC中JSON数据处理方式!!!

添加json依赖 <!--spring-json依赖--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.0</version></dependency> 注解 RequestBody&#xff1a;作…

prometheus 监控 Hyperledger Fabric 网络

本例中使用的 fabric 版本为 2.4.1 修改 orderer , peer 节点 docker-compose 文件 orderer 节点&#xff1a; environment:- ORDERER_METRICS_PROVIDERprometheus- ORDERER_OPERATIONS_LISTENADDRESS0.0.0.0:8443 ports:- 8443:8443peer 节点&#xff1a; environment:- CO…

AGX更新Jetpack后无法SSH报错:写入管道指定不存在

AGX更新Jetpack后无法SSH报错&#xff1a;写入管道指定不存在 报错内容 > IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! > Someone could be eavesdropping on you right now (man-in-the-middle attack)! > It is also possible that a host key has ju…

Python正则表达式(python系列29)

前言&#xff1a;在实际开发中&#xff0c;正则表达式主要用在模式匹配&#xff08;检查一个字符是否符合某种格式&#xff09;和处理复杂的文本数据&#xff0c;例如查找&#xff0c;替换&#xff0c;分割。 定义&#xff1a;使用元字符&#xff08;具有特殊意义的专用字符&a…

【数据库原理】(16)关系数据理论的函数依赖

一.函数依赖的概念 函数依赖是关系数据库中核心的概念&#xff0c;它指的是在属性集之间存在的一种特定的关系。这种关系表明&#xff0c;一个属性集的值可以唯一确定另一个属性集的值。 属性子集&#xff1a;在关系模式中&#xff0c;X和Y可以是单个属性&#xff0c;也可以是…

scVI与MultiVI

scVI&#xff1a;https://docs.scvi-tools.org/en/stable/user_guide/models/scvi.html MultiVI&#xff1a;https://docs.scvi-tools.org/en/stable/user_guide/models/multivi.html 目录 scVI生成推理任务 MultiVI生成推理 scVI single cell variational inference提出了一个…

elementui dialog 回车时却刷新整个页面

到处都是坑&#xff0c;这个坑填完另一个坑还在等你。。。坑坑相连&#xff0c;坑坑不同。。。 使用el-dialog弹出一个表单&#xff0c;当我无意间敲到回车键时&#xff0c;整个页面被刷新了&#xff0c;又是一脸的懵逼。。。 经过查找文档发现解决方案为上述截图标记。。。 e…

科锐16位汇编学习笔记 03 汇编指令

指令种类 数据传送指令算数运算类指令位操作类指令串操作类指令控制转移类指令处理器控制类指令 数据传送类指令 传送类指令不影响标志位&#xff0c;**除了标志位传送指令外。** 传送指令MOV&#xff08;move&#xff09; 说明 ​ 把一个字节或字的操作数从源地址传送至…

用golang 实现给图片添加文字水印

package mainimport ("fmt""github.com/golang/freetype""image""image/draw""image/jpeg""io""os""time" )func main() {// 打开原始图片file, err : os.Open("004.jpeg")if err …

解决Qt Creator中文乱码的问题

方法1 使用QStringLiteral()包裹中文字符串 QString str1"中文测试&#xff01;"; QString str2QStringLiteral("中文测试&#xff01;");方法2 #if _MSC_VER > 1600//MSVC2015>1899,MSVC_VER14.0 #pragma execution_character_set("utf-8&qu…

L1-011 A-B(Java)

题目 本题要求你计算A−B。不过麻烦的是&#xff0c;A和B都是字符串 —— 即从字符串A中把字符串B所包含的字符全删掉&#xff0c;剩下的字符组成的就是字符串A−B。 输入格式&#xff1a; 输入在2行中先后给出字符串A和B。两字符串的长度都不超过10的四次方&#xff0c;并且…

软文营销无效的原因,这些细节容易被忽略

不管你是卖产品还是做服务&#xff0c;不管是大公司还是小企业&#xff0c;都需要软文营销&#xff0c;然而营销也有好坏之分&#xff0c;好的营销会给客户带来更多企业和利润&#xff0c;无效营销不仅会耽误市场竞争的效率还会带来负面影响&#xff0c;今天媒介盒子就来和大家…

SpringMVC执行流程

SpringMVC执行流程 具体步骤 第一步&#xff1a;发起请求到前端控制器(DispatcherServlet) 第二步&#xff1a;前端控制器请求HandlerMapping查找 Handler 第三步&#xff1a;处理器映射器HandlerMapping向前端控制器返回Handler&#xff0c;HandlerMapping会把请求映射为Ha…

HTML---JQurey的基本使用

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 本章目标 &#xff08;1&#xff09;能够搭建jQuery开发环境 &#xff08;2&#xff09;使用ready( )方法加载页面、掌握jQuery语法 使用addClass( )方法和css( )方法为元素添加CSS样式使用n…