STL 关于vector的细节,vector模拟实现【C++】

文章目录

  • vector成员变量
  • 默认成员函数
    • 构造函数
    • 拷贝构造
    • 赋值运算符重载函数
    • 析构函数
  • 迭代器
    • begin
    • end
  • size和capacity
  • resize
  • reserve
  • [ ]
  • push_back
  • pop_back
  • insert
  • erase
  • swap

在这里插入图片描述

vector成员变量

在这里插入图片描述
_start指向容器的头,_finish指向容器当中有效数据的下一个位置,_endofstorage指向整个容器的尾

默认成员函数

构造函数

		//构造函数vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}

拷贝构造

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

深拷贝版本一:

		//拷贝构造(深拷贝)vector(const vector<T> & v)//v1= v//vector( vector * this ,const vector<T>& v){//开空间this->_start =  new T[size(T) * v.capacity()];//拷贝数据memcpy(this->_start, v._start, sizeof(T) * v.size()  );this->_finish = v._start + v.size();//更新_finishthis->_endofstorage = v._start + v.capacity();//更新 _endofstorage}

注意: 不能使用memcpy函数
如果vector存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用memcpy函数可以
但当vector存储的数据是需要进行深拷贝的自定义类型时,不能使用memcpy
举个例子
如果vector存储的数据是string类的时候

void test_vector9(){vector<string> v;v.push_back("111111111111111");v.push_back("222222222222222");v.push_back("333333333333333");v.push_back("444444444444444");v.push_back("555555555555555");//memcpy拷贝出现了问题for (auto & e : v) //string拷贝代价比较大{cout << e << " ";}cout << endl;}

memcpy函数进行拷贝构造的话,那么reserve函数申请的tmp当中存储的每个string的成员变量的值,与vector 当中的每个对应的string成员都指向同一个字符串空间,
在这里插入图片描述

delete释放空间 ,如果是自定义类型时,依次调用数组每个对象的析构函数,再释放整个空间,也就是说tmp现在指向一块被释放的空间,即tmp是野指针
在这里插入图片描述

总结:
问题:vector是深拷贝 , 但是vector空间上存的对象是string的数组使用memcpy导致string对象的浅拷贝

如何解决:

		void reserve( size_t n){if (n > capacity())//扩容 {size_t sz = size();//用sz记录size T * tmp = new T[n];if (_start != nullptr) //如果原空间不为空再拷贝数据{//memcpy(tmp, _start, sizeof(T) * sz);//将_start的数据拷贝到tmp中for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];//调用string的赋值重载进行深拷贝}delete[] _start;//释放_start的空间 }_start = tmp; //将tmp的地址给_start,以便_finish和_endofstorage的更新_finish = _start + sz;//更新_finish_endofstorage = _start + n;//更新_endofstorage } }

在这里插入图片描述

结论: 如果vector当中存储的元素类型是内置类型(int)或浅拷贝的自定义类型(Date),可以使用memcpy函数进行进行拷贝构造,但如果vector当中存储的元素类型是深拷贝的自定义类型(string),则不可以使用memcpy函数

深拷贝版本二:

使用范围for(或是其他遍历方式)对容器v进行遍历,在遍历过程中将容器v中存储的数据一个个尾插过来即可。

		//拷贝构造第二种版本(深拷贝)vector( const vector<T>& v)//v1=v//vector( vector *this , const vector<T> v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){//开空间reserve(v.capacity());//拷贝数据for (auto e : v){push_back(e); //将v的数据插入到v1中}}

注意: 在使用范围for对容器v进行遍历的过程中,变量e就是每一个数据的拷贝,然后将e尾插到构造出来的容器当中。就算容器v当中存储的数据是string类,在e拷贝时也会自动调用string的拷贝构造(深拷贝),所以也能够避免出现与使用memcpy时类似的问题。

赋值运算符重载函数

vector的赋值运算符重载当然也涉及深拷贝问题,我们这里也提供两种深拷贝的写法:

先释放原来的空间,再开辟一块和容器v大小相同的空间,然后将容器v当中的数据一个个拷贝过来,最后更新_finish和_endofstorage的值即可。

深拷贝版本一

		//赋值重载版本一vector<T>  &  operator=(vector<T> v)//v1=v//	vector<T>& operator=(vector<T> *this , vector<T> v){//释放原来的空间delete [] _start;//开辟空间 _start = new T[v.capacity()];//拷贝数据 for (size_t i =0 ; i < v.size(); ++ i){ _start[i]= v[i];}//更行相关边界条件_finish = _start + v.size();_endofstorage = _start + v.capacity();return *this;}

首先在右值传参时并没有使用引用传参,因为这样可以间接调用vector的拷贝构造函数,然后将这个拷贝构造出来的容器v与左值进行交换,此时就相当于完成了赋值操作,而容器v会在该函数调用结束时自动析构。

深拷贝版本二(推荐)

  //赋值重载版本二vector<T> & operator= ( vector<T> v) //编译器接收右值的时候自动调用拷贝构造函数//vector<T>& operator= ( vector<T> *this ,vector<T> v)//v1=v{//this->swap(v)swap(v);//v1 和v交换return *this;}

版本二的理解:
在这里插入图片描述

析构函数

对容器进行析构时,首先判断该容器是否为空容器,若为空容器,则无需进行析构操作,若不为空,则先释放容器存储数据的空间,然后将容器的各个成员变量设置为空指针即可。

	//析构函数~vector(){//_start==nulllptr 就不需要析构了if (_start != nullptr){delete[]_start;_start = _finish = _endofstorage = nullptr;}}

迭代器

在这里插入图片描述

begin

vector当中的begin函数返回容器的首地址

普通版本

        iterator begin(){return _start;}

const版本

	 const_iterator begin() const{return _start;}

end

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

普通版本

		 iterator end(){return _finish;}

const 版本

	 const_iterator end() const{return _finish;}

size和capacity

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

在这里插入图片描述

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

resize

1、n > size

 将size扩大到n,扩大的数据为val,若val未给出,就用缺省值

 2、n < size

改变_finish的指向,直接将容器的size缩小到n即可

		 void resize(  size_t n ,  const T& val =  T()   )//缺省值是匿名对象,c++对内置类型进行了升级{//n<size 缩容if (n < size()){_finish = _start + n;}else	 //扩容{reserve(n);//插入数据while (_finish!=_start+n){*_finish = val;_finish++;}}}

注意: c++把内置类型也看作成类,它们也有默认构造函数,所以在给resize函数的参数val设置缺省值时,设置为T( )即可

reserve

1、n>capacity(),将capacity扩大到n或大于n。
2、n<capacity() ,什么也不做。

	void reserve( size_t n){if (n > capacity())//扩容 {size_t sz = size();//用sz记录size T * tmp = new T[n];if (_start != nullptr) //如果原空间不为空再拷贝数据{memcpy(tmp, _start, sizeof(T) * sz);//将_start的数据拷贝到tmp中delete[] _start;//释放_start的空间 }_start = tmp; //将tmp的地址给_start,以便_finish和_endofstorage的更新_finish = _start + sz;//更新_finish_endofstorage = _start + n;//更新_endofstorage } }

注意:
1 在进行操作之前需要提前记录当前容器当中有效数据的个数。

2 拷贝容器当中的数据时,不能使用memcpy函数进行拷贝

[ ]

const 版本

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

普通版本

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

push_back

要尾插数据首先得判断容器是否已满,若已满则需要先进行增容,然后将数据尾插到_finish指向的位置,再将_finish++即可

void push_back(const T & x ){//如果容量满了if (_finish == _endofstorage)//扩容{size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve (newcapacity);//扩容}*_finish = x;_finish++;//_finish指针后移}

pop_back

		void pop_back(){erase(  --end()  );}

insert

insert函数可以在所给迭代器pos位置插入数据,在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入,最后将数据插入到pos位置即可。

		 iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);	//如果容量满了,需要扩容 if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();//扩容会开辟一段新的空间 ,把数据从原空间拷贝到新空间,并且释放原空间,但是此时pos这个迭代器还是指向原空间//会导致pos迭代器失效 —更新pos迭代器size_t len = pos - _start;reserve(newcapacity);pos = _start + len;}//容量未满iterator end = _finish -1;//挪动数据while (end>=pos){*(end + 1) = *(end);--end;}//插入数据 (*pos) = x;_finish++;return pos;}

insert以后可能会出现迭代器失效
解决方案:再下一次使用迭代器之前,对迭代器重新赋值即可

erase

erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器释放为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可

		 //错误的版本//void erase(iterator pos)//{// assert(pos >= _start && pos < _finish);// iterator it = pos + 1;// while (it != _finish)//挪动数据// {//	 *(it - 1) = *(it);//	 it++;// }// _finish--;//}//正确的版本iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it!= _finish)//挪动数据{*(it - 1) = *(it);it++;}_finish--;return pos;}

erase 在使用的时候可能会有迭代器失效的问题
解决方案:我们可以接收erase函数的返回值(erase函数返回删除元素的后一个元素的新位置)

swap

swap函数用于交换两个容器的数据,我们可以直接调用库当中的swap函数将两个容器当中的各个成员变量进行交换即可。

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

注意: 这里调用库里的swap模板函数,需要在swap函数之前加上“std::”,告诉编译器在c++标准库寻找swap函数,否则编译器编译时会认为你调用的是正在实现的swap函数(就近原则)。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!

在这里插入图片描述

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

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

相关文章

【驱动开发day4作业】

头文件代码 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #define PHY_LED2_ADDR 0X50007000 #…

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境&#xff1a; Sybase版本&#xff1a;SQL Anywhere 8.0。 数据库故障&#xff1a; 数据库所在的设备意外断电后&#xff0c;数据库无法启动。 错误提示&#xff1a; 使用Sybase Central连接后报错&#xff1a; 数据库故障分析&#xff1a; 经过北亚企安数据恢复…

Android 面试题 应用程序结构 九

&#x1f525; 核心应用程序 Activity五个状态&#x1f525; Starting-> running-> paused-> stopped-> killed 启动状态&#xff08;Starting&#xff09;&#xff1a;Activity的启动状态很短暂&#xff0c;当Activity启动后便会进入运行状态&#xff08;Running…

数据库架构演变过程

&#x1f680; ShardingSphere &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&…

【JavaEE】博客系统前后端交互

目录 一、准备工作 二、数据库的表设计 三、封装JDBC数据库操作 1、创建数据表对应的实体类 2、封装增删改查操作 四、前后端交互逻辑的实现 1、博客列表页 1.1、展示博客列表 1.2、博客详情页 1.3、登录页面 1.4、强制要求用户登录&#xff0c;检查用户的登录状态 …

浏览器访问nginx转发打开oss上的html页面默认是下载,修改为预览

使用阿里云盒OSS上传了html页面&#xff0c;在nginx里配置跳转访问该页面时&#xff0c;在浏览器里直接默认下载了该页面&#xff0c;现在想实现预览功能&#xff0c;只需在nginx里的location里修改消息头的Content-Disposition为inline即可 注意要隐藏头信息proxy_hide_header…

万年历【小游戏】(Java课设)

系统类型 Java实现的小游戏 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea或eclipse 运行效果 更多Java课设系统源码地址&#xff1a;更多Java课设系统源码地址 更多Java小游戏运行效果展示&#xff1a;更多Java小游戏运行效果展…

pytest——断言后继续执行

前言 在编写测试用例的时候&#xff0c;一条用例可能会有多条断言结果&#xff0c;当然在自动化测试用例中也会遇到这种问题&#xff0c;我们普通的断言结果一旦失败后&#xff0c;就会出现报错&#xff0c;哪么如何进行多个断言呢&#xff1f;pytest-assume这个pytest的插件就…

TiProxy 原理和实现

说明 在上篇《TiProxy 尝鲜》 中做了一些实验&#xff0c;比如加减tidb节点后tiproxy可以做到自动负载均衡&#xff0c;如果遇到会话有未提交的事务则等待事务结束才迁移。 本次主要研究这样的功能在tiproxy中是如何实现的&#xff0c;本次分享内容主要为以下几部分&#xff…

【 Python 全栈开发 - 人工智能篇 - 45 】决策树与随机森林

文章目录 一、概念与原理1.1 决策树1.1.1 概念1.1.2 原理特征选择分割方法 1.1.3 优点与缺点1.1.4 Python常用决策树算法 1.2 随机森林1.2.1 概念1.2.2 原理1.2.3 优点与缺点1.2.4 Python常用随机森林算法 1.3 决策树与随机森林的比较1.3.1 相同之处1.3.2 不同之处 二、决策树算…

嵌入式开发:单片机嵌入式Linux学习路径

SOC&#xff08;System on a Chip&#xff09;的本质区别在于架构和功能。低端SOC如基于Cortex-M架构的芯片&#xff0c;如STM32和NXP LPC1xxx系列&#xff0c;不具备MMU&#xff08;Memory Management Unit&#xff09;&#xff0c;适用于轻量级实时操作系统如uCOS和FreeRTOS。…

Vue基础-综合案例(基于vue2)

一、目标 能够知道如何使用vue-cli创建vue项目能够知道如何在项目中安装与配置element-ui能够知道element-ui中常见组件的用法能够知道如何使用axios中的拦截器能够知道如何配置proxy接口代理 二、目录 vue-cli组件库axios拦截器proxy跨域代理用户列表案例 vue-cli 1.什么…

利用mysqldump实现分库分表备份的shell脚本

一、信息摘要 linux版本&#xff1a;CentOS 7.9 mysql版本&#xff1a;MySQL 5.7.36 脚本实现功能&#xff1a;利用mysqldump工具实现对mysql中的数据库分库备份&#xff0c;和对所备份数据库中的表分表备份 二、shell脚本 #!/bin/bash ######################### #File n…

[Linux]进程控制详解!!(创建、终止、等待、替换)

hello&#xff0c;大家好&#xff0c;这里是bang___bang_&#xff0c;在上两篇中我们讲解了进程的概念、状态和进程地址空间&#xff0c;本篇讲解进程的控制&#xff01;&#xff01;包含内容有进程创建、进程等待、进程替换、进程终止&#xff01;&#xff01; 附上前2篇文章…

使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)

文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析&#xff0c;我增加了一些额外的东西&#x…

Open3D (C++) ISS特征点提取

目录 一、算法原理1、原理概述2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人 一、算法原理 1、原理概述 内部形状描述子(ISS)是一种表示立体几何形状的方法,该算法含有丰富的几何特征信息,可以完成高质量的点云配准。设…

禁用右键菜单AMD Software: Adrenalin Edition

本文参考链接&#xff1a; 删除win11右键一级菜单的AMD驱动栏 - 哔哩哔哩 windows11 求助删除右键菜单方法_windows11吧_百度贴吧 Windows安装最新的AMD显卡驱动后&#xff0c;右键菜单会多出AMD Software: Adrenalin Edition。使用一些右键菜单管理工具也没能屏蔽禁用掉该功…

mybatis-config.xml-配置文件详解

文章目录 mybatis-config.xml-配置文件详解说明文档地址:配置文件属性解析properties 属性应用实例 settings 全局参数定义应用实例 typeAliases 别名处理器举例说明 typeHandlers 类型处理器environments 环境environment 属性应用实例 mappers配置 mybatis-config.xml-配置文…

运维高级--shell脚本完成分库分表

为什么要进行分库分表 随着系统的运行&#xff0c;存储的数据量会越来越大&#xff0c;系统的访问的压力也会随之增大&#xff0c;如果一个库中的表数据超过了一定的数量&#xff0c;比如说MySQL中的表数据达到千万级别&#xff0c;就需要考虑进行分库分表&#xff1b; 其…

iOS-持久化

目的 1.快速展示&#xff0c;提升体验 已经加载过的数据&#xff0c;用户下次查看时&#xff0c;不需要再次从网络&#xff08;磁盘&#xff09;加载&#xff0c;直接展示给用户 2.节省用户流量&#xff08;节省服务器资源&#xff09; 对于较大的资源数据进行缓存&#xf…