【C++】vector实现(深浅拷贝详细理解,迭代器失效)

news2024/9/27 12:10:10

🍅可以先去这个网站看一下个个函数的功能 本文不再详细介绍,vector的底层还是顺序表,我讲的很详细,建议没学过顺序表的先预习一下(主页搜索顺序表,还有配套习题)

C++网站关于vector的接口函数信息

目录

☃️1.简单框架搭建

☃️2.稍难函数和迭代器失效问题

☃️3.深浅拷贝的深度理解

☃️4.容易看不懂的函数的类型复盘和总结


☃️1.简单框架搭建

为了和库里面的vector不冲突,我们自定义一个命名空间

通过看vector的源码,我们发现他的成员变量有

最后一个就是指容量位置的迭代器

很多接口函数可以去文章首的网站里看,本文基本全部实现最常用最经典的 有分析价值的函数 

最基本的构造函数(不止一个 后面还有更复杂的) 析构函数

~vector()
		{
			delete[] _start;
			_start = nullptr;
			_finish = nullptr;
			_endstorage= nullptr;
		}

还有很短的begin() end()

运算符重载[ ]

T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

 很基础的功能 尾插

void push_back(T val)
		{
			if (capacity() == size())
			{
				//需要扩容
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = val;
			_finish++;
		}

 resize()

void resize(size_t n, T val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > size())
			{
				while ((n - size() + 1)--)
				{
					*_finish = val;
					_finish++;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

reserve()   这个函数很坑 这个代码目前是有问题的,但是在现在讲解的阶段实现成这样完全没问题

注意:扩容的时候最好 

void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(int) * oldsize);
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + oldsize;
				_endstorage= _start + n;
			}
			//如果n更小不需要扩容
		}

取capacity() empty() 

size_t  capacity()
		{
			return _endstorage- _start;
		}

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

尾删 

void pop_back()
		{
			assert(empty());
				_finish--;
		}

☃️2.稍难函数和迭代器失效问题

现在是稍微有点挑战难度的函数

这里面的allocator是内存管理器,目前我们还不需要关心这是什么 以后会更新一篇博客专门分析这个问题 

其实我们发现 构造函数有很多形式 现在实现稍微复杂一点的

第二个形式,n个val

val的类型是value_type,在文档里也可以查

这里的成员类型都标注的很清楚

 首先要初始化肯定要开空间,直接reserve(n),然后每一个都赋值成val

vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endstorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}

		}

看起来很美妙是不是,但是这个以后都是坑,因为之前说的reserve有问题


插入insert()

在pos迭代器位置插入val 首先判断pos是否合法

如果容量不够(_finish==_endstorage)就扩容,这里涉及到迭代器失效的问题

 请问这个时候你还敢用扩容之后的pos?

因为一般扩容都是异地,但是这个pos的位置就应该改变但是没变,很尴尬,这个问题不一定出现(如果原地扩容就不会有问题)但是谁能一定保证呢,最好还是更新一下pos的位置

容量没问题之后就开始挪动数据 把前面的顺延到后面,最后把空出来的pos位置填上,把_finish 的位置更新

iterator  insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			if (_finish == _endstorage)
			{
				size_t len = pos - _start;
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);

				// 扩容会导致pos迭代器失效,需要更新处理一下
				pos = _start + len;
			}

			// 挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;

			return pos;
		}

删除pos位置,还是先判断位置合法,然后向前覆盖,更新_finish 

为什么文档里的设计要有返回值?

其实可以试一下 实现:删除某个位置的元素,然后再去修改迭代器指针

此时程序会崩溃

为什么?

其实和刚才的pos问题一样,erase的时候pos还是不安全,此时的it是野指针,不能再对it++之类的操作

或者实现一下删除所有偶数

 其实一开始这个vector里面是1234应该是段错误,第二次是122345 但是结果却是1235

显然我们这样没有返回值的erase是不可以的

首先解释第一个段错误(一般Linux这样报错就是越界或者野指针问题)

当走完4 删除之后 _finish ++  但是啊it也++ ,一直向后走,永远没有和end()相等的时候,就会一直走下去 

 

这样就会少删除数据 

 但是怎么解决呢,千万不能根据某个具体情况去更改,不得不说还是大佬厉害,用一个返回值,返回删除元素的下一个位置的迭代器

iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);


			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *(begin);
				++begin;
			}

			--_finish;

			return pos;
		}

clear() swap() 函数就是小case啦

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

拷贝构造的几种写法

其实很秀的这里,我们最传统的写法就是自己开空间 然后拷贝

一个一个拷贝!不要memcpy(剧透:reserve的坑就是memcpy,思考一下为什么C++不继续沿用melloc?free?就是告诉你别再C了,用C++解决问题)

拷贝构造的几种写法
		v1(v2)
		vector(vector<T>& v)  //传统写法
		{
			_start = new T[v.capacity()];  //按照v的大小开空间
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];  //一个一个拷贝
			}
			_finish = _start+v.size();
			_endstorage =_start+v.capacity();
		}

还有一个稍微创新的传统写法,很狡猾嘛,但是可惜了reserve有问题

另一种传统方法
		vector(vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endstorage(nullptr)
		{
			reserve(v.capacity());
			for (auto& e : v)  //一定要加上&,不知道v里面元素类型,如果是自定义类型需要深拷贝
			{
				push_back(e);
			}
		}

来看看创新的写法

刚才看构造函数不还有用迭代器构造的嘛(第三种),我们就实现一下

 然后我直接实例化一个tmp对象,再tmp和this交换一下

//很新的方法
		template <typename Inputiterator>
		vector(Inputiterator first, Inputiterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		vector(vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp); //this 和 tmp 交换
		}

那么=运算符就很好写了,直接把v赋给this,但是很少数的情况会v=v,所以是否判断都可以

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

☃️3.深浅拷贝的深度理解

我们都知道浅拷贝就是以bit为单位,一个一个拷贝,但是一些指针问题会导致对同一块空间的两次释放,对于自定义类型 我们来看一下会出现什么神奇的事情

测试这段代码 

void test()
{	
        vector<vector<int>> vv;
		vector<int > v(5, 1); 
        vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);
	for (size_t  i = 0; i < vv.size(); i++)
		{
			for (size_t j = 0; j < vv[i].size(); j++)
			{
				cout << vv[i][j] << " ";
			}
			cout << endl;
	    }
}

 结果是

 分析,最开始是这样的

 然后开始push_back ,最开始vv的容量是0,所以需要扩容,但是扩一次是4个还不行,还得扩二倍就是八个, 扩容 调用reserve

 tmp前五个_start还是指向5个v,然后memcpy拷贝,然后delet[ ] _start 

 是不是问题就来了,我vector<int>都mb了啊,我的所有元素都变成野指针了.....

谁的锅!reserve()里面的memcpy,因为他是浅拷贝,如果拷贝的时候可以开一份空间就好了

那么就用赋值好了,_tmp[i]=_start[i] 不管是传统写法还是现代写法都会开空间的(v1=v2,是会开空间再拷贝的)

所以代码应该写成这样

	void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(int) * oldsize); //这个地方有问题,自定义类型的浅拷贝
					for (size_t  i = 0; i < oldsize; i++)
					{
						tmp[i]=_start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + oldsize;
				_endstorage = _start + n;
			}
			//如果n更小不需要扩容
		}

☃️4.容易看不懂的函数的类型复盘和总结

我们这里定义了模板T,这个T在vector <int>  的时候就是int  在vector<vector<int>> 的时候就是vector<int>

迭代器的类型就是和模板类型有关的

 

 

 [ ]的赋值重载就应该是T类型的,至于&就是赋值拷贝的基本操作了,我们之前说过可以不加&的,但是一般来讲我们都更喜欢用筷子吃饭(虽然直接用手也可以)

 他还有一个const成员函数的函数重载,当然返回值也要是const啦

 这个拷贝构造的意思就是用n个val初始化val的值 类型一定是模板类型,因为这个容器里元素类型就是模板T,加上const的意思就是val的值是不能在vector这个构造内部改变的,当然如果你不写那么我们默认是T(),这是一个匿名对象,对于int来说就是0,还有这里有一个引用,意思就是万一你传过来的是指针之类的,我也可以对你直接操作 无需考虑几级指针,形参的改变影不影响实参之类的

 push_back里面这个x同样的,形参的改变会影响实参

 这个函数的参数是一个对象v,他的类型和this的类型一样都是,&也是为了形参的改变影响实参

 其实也就是这个vector 的实现 有上面几个类型不好理解,其他的都很简单,C++的细节就是很多,希望大家不厌其烦,我们一起学好!

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

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

相关文章

雅思积累(十八)同义替换

同义替换&#xff1a;sophisticated —— complicatedspecific —— detailed —— particularquantity —— wealth —— volume —— numbersettle —— schedule —— arrange —— fixswift —— rapid —— fast —— quickexpertise —— professional knowledge —— sk…

【云原生kubernetes】k8s中job与cronjob使用详解

一、前言 job&#xff0c;顾名思义就是任务&#xff0c;job的概念在很多框架中都有&#xff0c;而且实际业务场景中也使用非常广泛&#xff0c;比如大家熟悉的hadoop&#xff0c;客户端可以向集群提交一个job&#xff0c;然后集群根据一定的调度策略来处理这个job&#xff1b; …

【2023unity游戏制作-mango的冒险】-6.关卡设计

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity游戏制作 ⭐mango的冒险关卡设计⭐ 文章目录⭐mango的冒险关卡设计⭐&#x1f468;‍&#…

软件测试之jira

Jira 1. Jira 概述 JIRA 是澳大利亚 Atlassian 公司开发的一款优秀的问题跟踪管理软件工具&#xff0c;可以对各种类型的问题进行跟踪管理&#xff0c;包括缺陷、任务、需求、改进等。JIRA采用J2EE技术&#xff0c;能够跨平台部署。它正被广泛的开源软件组织&#xff0c;以及…

更新 TKK 失败,请检查网络连接。谷歌翻译 translation插件不能用解决办法 亲测有效

谷歌翻译无法使用&#xff0c;谷歌回应解释是&#xff0c;谷歌翻译使用率过低&#xff0c;所以选择停止服务。网上也有说法&#xff0c;指出根本原因为&#xff0c;提供API接口的googleapis被墙&#xff0c;这导致js文件和字体资源无法加载。 这里提供两种解决办法 方案一 修…

枯燥迷茫?先来玩玩这些经典的计算机视觉项目

B站|公众号&#xff1a;啥都会一点的研究生 颜色检测 从检测颜色到绿幕应用&#xff08;用自定义视频或背景替换绿色背景&#xff09;&#xff0c;再到简单的照片编辑软件&#xff0c;构建颜色识别器是计算机视觉入门的一个很棒的项目 项目地址&#xff1a;https://github.…

PostgresSQL存储过程和触发器

在次之前首先要搞清楚一个概念 存储过程和触发器&#xff0c;是在基础sql语句之后的另一门语言&#xff0c;类似小学的加减乘除和奥数的关系&#xff0c;他们虽然都是数学&#xff0c;但是运算复杂度和定向思维都有了很大程度的不同 这篇文章不打算把存储过程和触发器事无巨细…

SAP 详细解析SCC4

事务代码&#xff1a;SCC4&#xff0c;选择一个客户端&#xff0c;点击进入&#xff0c;如图&#xff1a; 一、客户端角色 客户控制&#xff1a;客户的角色&#xff08;生产性&#xff0c;测试&#xff0c;...&#xff09; 此属性表示 R/3 系统中的客户端角色。其中可能包括…

简单分析Linux虚拟化KVM-Qemu之vhost-net

说明&#xff1a; KVM版本&#xff1a;5.9.1 QEMU版本&#xff1a;5.0.0 工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 让我们先来看看问题的引入&#xff0c;在之前的virtio系列文章中&#xff0c;网络虚拟化的框架如下图所示&#xff1a; ​ ​ Qemu中的…

WebRTC-NACK、Pacer和拥塞控制和FEC

NACK机制发送端实现NACK的三个重点流程&#xff1a;发送RTP报文&#xff0c;实时存储报文到packet_history_队列处理接收到的RTCP NACK报文把nack包里的序号放到nack_sequence_numbers丢包队列重发NACK反馈的RTP报文重发报文这里有三点需要注意&#xff1a;1&#xff09;会判断…

Allegro如何查看PCB上器件的库路径操作指导

Allegro如何查看PCB上器件的库路径操作指导 在做PCB设计的时候,有时需要检查PCB上器件使用的库的路径是否正确,Allegro支持快速将PCB上所有器件的库路径都列出来 如下图 如何显示这个报表,具体操作如下 点击Tools点击Report

蓝桥杯-求和问题

蓝桥杯-求和问题1、问题描述2、解法一&#xff1a;暴力解法(两层循环)3、解法二&#xff1a;结合律(一层循环解决)1、问题描述 给定 n 个整数 a1,a2,...,ana_1,a_2,...,a_na1​,a2​,...,an​&#xff0c;求它们两两相乘再相加的和&#xff0c;即&#xff1a; Sa1.a2a1.a3...a…

Presto 在美图的实践

导读&#xff1a;本文的主题是Presto高性能引擎在美图的实践&#xff0c;首先将介绍美图在处理ad-hoc场景下为何选择Presto&#xff0c;其次我们如何通过外部组件对Presto高可用与稳定性的增强。然后介绍在美图业务中如何做到合理与高效的利用集群资源&#xff0c;最后如何利用…

项目管理的主要内容包括哪些?盘点好用的项目管理系统软件

阅读本文您将了解&#xff1a;1、项目管理的主要内容包括哪些2、好用的项目管理软件 项目管理是为了实施一个特定目标&#xff0c;所实施的一系列针对项目要素的管理过程&#xff0c;包括过程、手段以及技术等。 通过项目管理&#xff0c;我们能够提前安排和控制项目的时间、…

深度解析:我如何用300并发把数据库压挂了

问&#xff1a;为什么300的并发能把支持最大连接数4000数据库压死&#xff1f; 买了一台数据库&#xff0c;最大连接数的参数是 4000&#xff0c;看起来很棒&#xff01;但是 cpu 和内存并不咋好&#xff01;是 2c4g的超低配制。但是想着反正业务量也不大&#xff0c;不如先扛…

【vulhub漏洞复现】CVE-2018-2894 Weblogic任意文件上传漏洞

一、漏洞详情影响版本weblogic 10.3.6.0、weblogic 12.1.3.0、weblogic 12.2.1.2、weblogic 12.2.1.3WebLogic是美国Oracle公司出品的一个application server&#xff0c;确切的说是一个基于JAVAEE架构的中间件&#xff0c;WebLogic是用于开发、集成、部署和管理大型分布式Web应…

Oracle Primavera P6 登录提示错误“该用户已经登录“(SQLite)

目录 引言 解决思路 使用工具 处理办法 引言 在使用Oracle Primavera P6 非正常退出后&#xff0c;Professional再次登录或出现异常&#xff0c;体现为“该用户已经登录。请使用另一个用户名” 以上为近期一个朋友请教的问题&#xff0c;为了给后续出现同样问题朋友给予解…

有限元中四面体的一些积分公式

文章目录有限元中四面体的相关积分公式有限元中四面体的相关积分公式 在 xyzxyzxyz 坐标系中通过四个点 (xi,yi,zi),(xj,yj,zj),(xm,ym,zm),(xp,yp,zp)(x_i, y_i, z_i), (x_j, y_j, z_j), (x_m, y_m, z_m), (x_p, y_p, z_p)(xi​,yi​,zi​),(xj​,yj​,zj​),(xm​,ym​,zm​…

解决PyCharm下OpenCV没有自动补全、函数提示的问题!

Content找到Python环境下的OpenCV安装目录中的“cv2.pyd”文件复制cv2.pyd文件到site-packages文件夹中重启PyCharm&#xff0c;cv2就可以正常使用了最近使用PyCharm编写一段需要使用Opencv库的代码&#xff0c;却发现cv2没有自动补全和函数提示了。博主自己找到以下解决办法&a…

ThreadLocal使用

1、简介ThreadLocal类用来提供线程内部的局部变量&#xff0c;不同的线程之间不会相互干扰这种变量在多线程环境下访问&#xff08;通过get和set方法访问&#xff09;时能保证各个线程的变量相对独立于其他线程内的变量在线程的生命周期内起作用&#xff0c;可以减少同一个线程…