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();//更新_finish
			this->_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函数(就近原则)。

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python零基础入门(九)——函数,类和对象

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python入门专栏&#xff1a;《Python入门》欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 码字不易&#xff0c;如果觉得文章不…

❤️创意网页:萌翻少女心的果冻泡泡 - 创造生动有趣的视觉效果

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

【UE4】局域网多人联机 Demo

效果 亲测可以打包后在两个电脑上联机运行&#xff08;前提是在同一个局域网内&#xff0c;互相能ping通&#xff09; 步骤 1. 首先新建一个第三人称角色模板工程 2. 在多玩家选项中&#xff0c;设置玩家数量为2 选择在新建编辑器窗口中运行 3. 新建一个父类为Character的蓝…

【1.1】Java微服务:初识微服务

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 微服务 ✨特色专栏&#xff1a; 知识分享 &#x…

大数据Flink(五十三):Flink流处理特性、发展历史以及Flink的优势

文章目录 Flink流处理特性、发展历史以及Flink的优势 一、Flink流处理特性 二、发展历史

数据结构入门指南:链表(新手避坑指南)

目录 前言 1.链表 1.1链表的概念 1.2链表的分类 1.2.1单向或双向 1.2.2.带头或者不带头 1.2.33. 循环或者非循环 1.3链表的实现 定义链表 总结 前言 前边我们学习了顺序表&#xff0c;顺序表是数据结构中最简单的一种线性数据结构&#xff0c;今天我们来学习链表&#x…

基于RK3588+AI的边缘计算算法方案:智慧园区、智慧社区、智慧物流

RK3588 AI 边缘计算主板规格书简介 关于本文档 本文档详细介绍了基于Rockchip RK3588芯片的AI边缘计算主板外形、尺寸、技术规格&#xff0c;以及详细的硬件接口设计参考说明&#xff0c;使客户可以快速将RK3588边缘计算主板应用于工业互联网、智慧城市、智慧安防、智慧交通&am…

联想拯救者如何开启独显直连

不同机型有不同的切换方式&#xff0c;下面就分别给大家讲一下&#xff1a; 显卡模式切换方式一&#xff1a; 打开联想电脑管家&#xff0c;选择游戏模式&#xff0c;在左侧菜单栏选择显卡模式&#xff0c;然后就能看到显卡的输出模式了&#xff0c;默认是混合模式&#xff0c…

MDK5__配色方案的修改

一、必要的知识 与MDK主题相关的文件有两个&#xff0c;在X:\Keil_v5\UV4路径下&#xff1a; global.propglobal.prop.def其中global.prop.def是系统默认的主题配置 如果修改过字体等&#xff0c;系统会生成一个global.prop。 二、修改的步骤 1、打开工程 菜单 Edit 下 Con…

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

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

【JVM】详解JVM的五大内存模型、可能出现的异常以及堆栈引用易错点

文章目录 1、堆(线程共享)2、方法区(线程共享)3、虚拟机栈&#xff08;线程私有&#xff09;4、本地方法栈(线程私有)5、程序计数器(线程私有)6、易错点 源自&#xff1a;深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践&#xff08;第3版&#xff09; 周志明 1、堆(线程…

使用克拉默法则进行三点定圆(二维)

目录 1.二维圆2.python代码3.计算结果 本文由CSDN点云侠原创&#xff0c;爬虫网站请自重。 1.二维圆 已知不共线的三个点&#xff0c;设其坐标为 ( x 1 , y 1 ) (x_1,y_1) (x1​,y1​)、 ( x 2 , y 2 ) (x_2,y_2) (x2​,y2​)、 ( x 3 , y 3 ) (x_3,y_3) (x3​,y3​)&#xf…

Ubuntu-文件和目录相关命令一

&#x1f52e;linux的文件系统结构 ⛳目录结构及目录路径 &#x1f9e9;文件系统层次结构标准FHS Filesystem Hierarchy Standard(文件系统层次结构标准&#xff09; Linux是开源的软件&#xff0c;各Linux发行机构都可以按照自己的需求对文件系统进行裁剪&#xff0c;所以众多…

Python - OpenCV实现摄像头人脸识别(亲测版)

要使用Python 3和OpenCV进行摄像头人脸识别&#xff0c;您可以按照以下步骤进行操作&#xff1a; 0.安装OpenCV软件 去官网直接下载安装即可,如果是C使用OpenCV&#xff0c;需要使用编译源码并配置环境变量。 1.安装OpenCV库 在命令行中输入以下命令&#xff1a; pip inst…

渗透测试基础知识(1)

渗透基础知识一 一、Web架构1、了解Web2、Web技术架构3、Web客户端技术4、Web服务端组成5、动态网站工作过程6、后端存储 二、HTTP协议1、HTTP协议解析2、HTTP协议3、http1.1与http2.0的区别4、HTTP协议 三、HTTP请求1、发起HTTP请求2、HTTP响应与请求-HTTP请求3、HTTP响应与请…

具有电动驱动的四足机器人模型研究(SimulinkMatlab代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

[NLP]LLM高效微调(PEFT)--LoRA

LoRA 背景 神经网络包含很多全连接层&#xff0c;其借助于矩阵乘法得以实现&#xff0c;然而&#xff0c;很多全连接层的权重矩阵都是满秩的。当针对特定任务进行微调后&#xff0c;模型中权重矩阵其实具有很低的本征秩&#xff08;intrinsic rank&#xff09;&#xff0c;因…

ajax axios json

目录 一、ajax概述 1. 概念 2. 实现方式 &#xff08;1&#xff09;原生的JS实现方式&#xff08;了解&#xff09; &#xff08;2&#xff09; JQeury实现方式 二、axios 介绍 三、axios使用 1. axios 发送get/post请求 2. axios验证用户名称是否存在 四、json 1. …

2023牛客暑期多校-J-Qu‘est-ce Que C‘est?(DP)

题意&#xff1a; 给定长度为n的数列,要求每个数都在的范围&#xff0c;且任意长度大于等于2的区间和都大于等于0&#xff0c;问方案数。。 思路&#xff1a; 首先要看出是dp题&#xff0c;用来表示遍历到第i位且后缀和最小为x的可行方案数&#xff08;此时的后缀可以只有最…

[golang gin框架] 42.Gin商城项目-微服务实战之后台Rbac微服务角色增删改查微服务

一.重构后台Rbac用户登录微服务功能 上一节讲解了后台Rbac微服务用户登录功能以及Gorm数据库配置单独抽离&#xff0c;Consul配置单独抽离&#xff0c;这一节讲解后台Rbac微服务角色增删改查微服务功能&#xff0c;Rbac微服务角色增删改查微服务和后台Rbac用户登录微服务是属于…