【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优化 更新字…

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…

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…

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

不管你是卖产品还是做服务&#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…

echarts设置tooltip的层级

echarts设置tooltip的层级 tooltip: {trigger: "axis",extraCssText: z-index:3, // 修改层级borderColor: "rgba(0, 170, 255)",}, 完整的option示例如下&#xff1a; option {tooltip: {trigger: "axis",extraCssText: z-index:3,axisPoin…

Python 架构模式:附录 A 到 E

附录 A&#xff1a;摘要图和表 原文&#xff1a;Appendix A: Summary Diagram and Table 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 这是我们在书的最后看到的架构&#xff1a; 表 A-1 总结了每个模式及其功能。 表 A-1. 我们的架构组件及其功能 层组件描述领域…

Linux 进程和计划任务管理

一 内核功用 进程管理、内存管理、文件系统、网络功能、驱动程序、安全功能等 1 程序 是一组计算机能识别和执行的指令&#xff0c;运行于电子计算机上&#xff0c;满足人们某种需求的信息化工具 用于描述进程要完成的功能&#xff0c;是控制进程执行的指令集 2 进程 运行…

1877_SHA512校验的使用

全部学习汇总&#xff1a; GreyZhang/toolbox: 常用的工具使用查询&#xff0c;非教程&#xff0c;仅作为自我参考&#xff01; (github.com) 之前下载很多软件&#xff0c;尤其是开源软件的文件包的时候通常会看到一个校验文件。之前下载的时候我一般都是直接忽略&#xff0c;…

金和OA C6 CarCardInfo.aspx SQL注入漏洞复现

0x01 产品简介 金和网络是专业信息化服务商,为城市监管部门提供了互联网+监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。 0x02 漏洞概述 金和OA C6 CarCardInfo.aspx接口处存在SQL注入漏洞,攻击者除了可以利用 SQL 注入漏洞…

SpringIOC之support模块EmbeddedValueResolutionSupport

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

小梅哥Xilinx FPGA学习笔记22——ip核之FIFO

目录 一&#xff1a;章节说明 1.1 FIFO IP简介 1.2 FIFO Generato IP 核信号框图 1.3 实验任务 二&#xff1a;FIFO 写模块设计 2.1 简介 2.2 模块框图 2.3 模块端口与功能描述 2.4 写模块代码 三 FIFO 读模块设计 3.1 简介 3.2 模块框图 3.3 模块端口与功…

OpenAI ChatGPT-4开发笔记2024-03:Chat之Tool和Tool_Call(含前function call)

Updates on Function Calling were a major highlight at OpenAI DevDay. In another world,原来的function call都不再正常工作了&#xff0c;必须全部重写。 function和function call全部由tool和tool_choice取代。2023年11月之前关于function call的代码都准备翘翘。 干嘛…