vector模拟实现下篇及迭代器失效和深浅拷贝问题详解

news2024/11/24 0:25:00

文章目录

  • 1:构造函数
    • 1.1默认构造函数
    • 1.2迭代器构造
    • 1.3用n个val构造
    • 1.4拷贝构造
  • 2:operator=
  • 3:析构函数和clear
  • 4:迭代器失效问题
    • 4.1:删除偶数
  • 深浅拷贝

1:构造函数

1.1默认构造函数

		vector()
			:_start(nullptr)
			,_end(nullptr)
			,_endofstorage(nullptr)
		{}

1.2迭代器构造

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				pushback(*first);
				++first;
			}
		}

在类模板中使用函数模板。

1.3用n个val构造

		vector(size_t n, const T& val = T())
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				pushback(val);
			}
		}

我们测试一下

	void test1()
	{
		vector<char> v(10, 1);

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i];
		}
	}

image-20221215142047255

这个报错产生在 image-20221215142105794

分析:

用10个1构造vector,1是整形,那么T就会推演为int类型,10也是整形,但是这个n个val构造的n是size_t类型,int转为size_t需要发生隐式类型转换,而1.2里面我们写的迭代器,inputiterator只是个名字,int可以被这个名字表达,所以编译器不会去找要转换的而是直接调用1.2的构造函数,但是int如果作为迭代器,*it访问的时候就会出错,也就是非法的间接寻址。因此为了解决这个问题,我们需要再重载一个构造函数

		vector(int n, const T& val = T())
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				pushback(val);
			}
		}

1.4拷贝构造

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_end, v._end);
			std::swap(_endofstorage, v._endofstorage);
		} 		
        vector(const vector<T>& v)
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> temp(v.begin(), v._end());
			swap(v);
		}

和string一样,用库里面的swap进行深拷贝交换。

2:operator=

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

和string一样,传值的形参不改变实参,形参出了作用域自动销毁对象。而且这里的swap传形参的引用,形参拿到原本的vector地址,当他出了函数作用域的时候顺便帮vector也析构。

3:析构函数和clear

		~vector()
		{
			delete[] _start;
			_start = _end = _endofstorage = nullptr;
		}
		void clear()
		{
			_end = _start;
		}

clear是清空数据

4:迭代器失效问题

上篇我们设计了insert和erase成员函数。

		void insert(iterator pos, const T& val)
		{
			assert(pos < _end);
			assert(pos >= _start);
			if (_end == _endofstorage)
			{
				size_t len = pos - _start;
				int newcapacity = capacity() > 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;
			}
			iterator _finish = _end - 1;
			while (_finish >= pos)
			{
				*(_finish + 1) = *_finish;
				--_finish;
			}
			*pos = val;
			++_end;
		}
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);


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

			--_finish;

			return pos;
		}

使用len是解决了内部迭代失效的原因。

	void test2()
	{
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		std::vector<int>::iterator it = find(v.begin(), v.end(), 3);
		if (it != v.end())
		{
			v.erase(it);
		}
		cout << *it << endl;
		for (auto e : v)
		{
			cout << e << ' ';
		}
	}

当删除了it原本的位置后,

image-20221215145610834

直接崩溃,不打印。

而看看g++

image-20221215145640200

vs报错,g++不报错。

迭代器失效,因此在g++源码里面,是这样实现的。

将erase和insert的返回值设定为iterator。这样可以更新位置后再继续使用。

it = v.erase(it);

4.1:删除偶数

image-20221215151355939

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


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

			--_end;

			return pos;
		}

如果是1234,如果是最后一个位置是偶数要删除,那么begin就会从pos+1的位置删除也就是和end一样的位置,那么我们的erase都不会进去,直接end–了,然后此时it还要++,也就是2个迭代器错过了,这样就会一直判断下去导致不断越界。问题就是最后一个数是偶数。

如果是122345, image-20221215152045383

当it是第一个2的时候,会erase,挪动数据,将下一个2移动到前面一个2的位置,然后还要++it,这样的话就会导致第二个2没有判断到,所以结果不符合。

如果单纯的解决办法是如下

else
{
++it;
}

这样子可以解决末尾有偶数的问题(段错误)。

但是我们参考的是sgi,sgi是linux的,采用的迭代器是原生指针,而vs是windows下的。 迭代器不是原生指针。所以erase和insert等涉及到迭代器失效的需要用迭代器作为返回值。

	//void test_vector6()
	//{
	//	// 要求删除所有偶数
	//	std::vector<int> v;
	//	v.push_back(1);
	//	v.push_back(2);
	//	v.push_back(2);
	//	v.push_back(3);
	//	v.push_back(4);

	//	std::vector<int>::iterator it = v.begin();
	//	while (it != v.end())
	//	{
	//		if (*it % 2 == 0)
	//		{
	//			it = v.erase(it);
	//		}
	//		else
	//		{
	//			++it;
	//		}
	//	}

	//	for (auto e : v)
	//	{
	//		cout << e << " ";
	//	}
	//	cout << endl;
	//}

深浅拷贝

	void test_vector9()
	{
	/*	vector<vector<int>> vvRet = Solution().generate(5);

		for (size_t i = 0; i < vvRet.size(); ++i)
		{
			for (size_t j = 0; j < vvRet[i].size(); ++j)
			{
				cout << vvRet[i][j] << " ";
			}
			cout << endl;
		}
		cout << endl;*/

		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;
		}
		cout << endl;
	}
}

看下面的代码,创建包含5个1的vector,再往一个vector里面插入这种vector,然后打印这个大的vector。

image-20221215154542562

运行结果报错,还出现了随机数。这是为什么?

在前面章节我提到过,当按字节序拷贝的时候会发生浅拷贝,按字节序拷贝的函数有memcpy,而memcpy是在reserve过程的,这里面当我们在总的vector里面插入小的vector的时候一定会发生扩容,当发生扩容的时候就会reserve,将旧数据拷贝到新空间。

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				//需要扩容
				T* temp = new T[n];
				//使用new可以调用默认构造,比malloc要舒服的多。
				//要注意如果start为空的时候,拷贝不了任何东西所以先判断start
				if (_start)
				{
					memcpy(temp, _start, sizeof(T) * oldsize);
					delete[] _start;
			    }
				_start = temp;
				_end = _start + oldsize;
				_endofstorage = _start + n;
			}
		}

image-20221215154852024

image-20221215154914444

当开辟好新空间,调用memcpy的时候,是发生的字节序拷贝,将上面4个单元格内容拷贝到下面的几个单元格里面,并且用的是字节序拷贝,所以下面的单元格里面的start都和上面的4个小单元格里面的start指向内容一样,是同一块空间,当调用delete的时候,就会析构掉上面的整体,会调用整体(自定义类型vector int)的析构函数,析构掉每个单元,这时每个小单元格指向的内容都变成了随机的,temp里的单元格和上面的单元格指向内容是一样的,所以temp赋值给下面的总体的start迭代器的时候,相当于把随机值给他,所以导致了这个结果。

本质上就是因为reserve的memcpy导致了浅拷贝,内容先被释放,成为随机空间,又把随机空间赋值给新空间的start导致出错。

解决办法

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldSize = size();
				T* tmp = new T[n];

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*oldSize);
					for (size_t i = 0; i < oldSize; ++i)
					{
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = tmp + oldSize;
				_endofstorage = _start + n;
			}
		}

使用赋值,赋值的话是深拷贝。不会引起上面问题。

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

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

相关文章

手动安装Kylin5.0版本的过程

官方文档 https://kylin.apache.org/目前kylin3,4版本是有docker版本和安装包的,5.0只有docker没有安装包 安装包 https://kylin.apache.org/download/安装kylin5.0 Kylin5.0文档拉取镜像 docker pull apachekylin/apache-kylin-standalone:5.0.0运行镜像 docker run -d \ …

linux-jdk、nginx

一、安装nginx Nginx是一个web服务器也可以用来做负载均衡及反向代理使用&#xff0c; 目前使用最多的就是负载均衡&#xff0c;这篇文章主要介绍了centos8 安装 nginx Nginx是一种开源的高性能HTTP和反向代理服务器&#xff0c;负责处理Internet上一些最大站点的负载。 它…

数据结构——重点代码汇总

顺序表 设计算法&#xff0c;从顺序表L中删除值为x的元素。要求算法的时间复杂度为O(n)&#xff0c;空间复杂度为O(1)。设计算法&#xff0c;判断一个字符串是否是回文。如abc3cba是回文序列&#xff0c;而1331不是回文序列。从顺序存储结构的线性表a中删除第i个元素起的k个元…

SuperMap GIS 倾斜摄影数据处理 QA

一、倾斜摄影数据简介 倾斜摄影&#xff08;Oblique photography&#xff09;是指由一定倾斜角度的航摄相机所获取的影像。倾斜摄影技术是国际摄影测量领域近十几年发展起来的一项高新技术&#xff0c;该技术通过从一个垂直、四个倾斜、五个不同的视角同步采集影像&#xff0c…

深度学习训练营之天气识别P3

深度学习训练营之天气识别原文链接环境介绍前置工作设置GPU导入数据数据查看数据预处理加载数据可视化数据检查数据配置数据集prefetch()功能详细介绍&#xff1a;构建CNN网络编译模型训练结果可视化原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记…

卫龙上市首日破发:高瓴、红杉、腾讯等账面亏损,刘卫平为董事长

12月15日&#xff0c;卫龙美味全球控股有限公司&#xff08;下称“卫龙”&#xff0c;HK:09985&#xff09;在港交所上市。本次上市&#xff0c;卫龙的发行价格为10.56港元/股&#xff0c;募资总额约为10.18亿港元&#xff0c;募资净额约为8.99亿港元。 上市首日&#xff0c;卫…

Web3中文|NFT如何助力项目进入Web3?

自NFT流行以来&#xff0c;一直有人将这些由区块链驱动的代币视作贯彻人类精神的最终疗法。 但是NFT真的都存储在区块链上了吗&#xff1f;如果是这样的话&#xff0c;怎么还会出现百万NFT被盗的事件呢&#xff1f; 如果你也想过这些问题&#xff0c;那么请相信我&#xff0c…

在现有项目里面添加 TSX 并编写组件过程记录

首先需要安装编译支持和 vite 支持插件 ## babel 基础插件 yarn add vue/babel-plugin-jsx -D## 项目用 vite 构建的就需要按照这个 yarn add vitejs/plugin-vue-jsx -D 使用插件 按照 babel-plugin-jsx 的指引在 babel 配置项中启用插件&#xff1a; {"plugins":…

Linux操作系统常见问题汇总

1.系统启动流程。 uboot -> kernel -> 根文件系统。 uboot第一阶段属于汇编阶段&#xff1a; 定义入口&#xff08;start.S&#xff09;&#xff1a;uboot中因为有汇编阶段参与&#xff0c;因此不能直接找main.c。 设置异常向量&#xff1a;当硬件发生故障的时候CPU会…

K8s Dashboard 部署

1、下载 Dashboard 的 yaml 文件 点击链接下载 YAML 文件 2、源码包中 yaml 文件在哪里 3、修改 yaml 文件 vim recommended.yaml... kind: Service apiVersion: v1 metadata:labels:k8s-app: kubernetes-dashboardname: kubernetes-dashboardnamespace: kubernetes-dashboard…

Java web 2022跟学尚硅谷(十) 后端基础 书城

Java web 2022跟学尚硅谷十 后端基础 书城验证码kaptcha和缓存cookie简单了解cookie步骤简单创建cookie的样例代码CookieServlet01hello.html页面结果Cookie保存结果第二次请求cookie的APIKaptcha验证码使用步骤显示效果验证码的校验相关类KaptchaServlet01书城1.2正则表达式正…

C语言经典题目—单链表求和

练习的题目<单链表求和>题目难度较小。 1、题目描述 小明输入了一个长度为 n 的数组&#xff0c;他想把这个数组转换成链表&#xff0c;链表上每个节点的值对应数组中一个元素的值&#xff0c;然后遍历链表并求和各节点的值。输入描述&#xff1a; 第一行输入一个正整数…

Linux下内存空间分配、物理地址与虚拟地址映射

一、Linux内核动态内存分配与释放 1.1 kmalloc函数 Kmalloc分配的是连续的物理地址空间。如果需要连续的物理页&#xff0c;可以使用此函数&#xff0c;这是内核中内存分配的常用方式&#xff0c;也是大多数情况下应该使用的内存分配方式。 传递给函数的最常用的标志是GTP_A…

火山引擎DataTester:无需研发人力,即刻开启企业A/B实验

近日&#xff0c;火山引擎A/B测试平台—— DataTester 对产品内A/B实验的“可视化编辑器”进行了新的升级&#xff0c;对交互、预览、Xpath的层次结构视图等能力均做了优化。 据介绍&#xff0c;火山引擎DataTester的可视化编辑器&#xff0c;可以让用户无需编写任何代码&#…

基于java+springboot+mybatis+vue+mysql的CSGO游戏比赛赛事管理系统

项目介绍 CSGO赛事管理系统利用网络沟通、计算机信息存储管理&#xff0c;有着与传统的方式所无法替代的优点。比如计算检索速度特别快、可靠性特别高、存储容量特别大、保密性特别好、可保存时间特别长、成本特别低等。在工作效率上&#xff0c;能够得到极大地提高&#xff0…

基于JAVA+SpringMVC+Mybatis+Vue+MYSQL的大健康老年公寓管理系统

项目介绍 本系统采用java语言开发&#xff0c;后端采用ssm框架&#xff0c;前端采用vue技术&#xff0c;数据库采用mysql进行数据存储。 管理员后台页面&#xff1a; 功能&#xff1a;主页、个人中心、护理人员管理、收费标准管理、接待登记管理、房间信息管理、床位信息管理…

ASEMI肖特基二极管MBR30200FCT封装,MBR30200FCT体积

编辑-Z ASEMI肖特基二极管MBR30200FCT参数&#xff1a; 型号&#xff1a;MBR30200FCT 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;200V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;140V 最大直流阻断电压&#xff08;VDC&#xff09…

Android 相机预览 横屏竖屏 -- 显示

相机在设备上处于固定位置&#xff0c;无论设备是否 是手机、平板电脑或计算机。当设备方向更改时&#xff0c; 相机方向更改。常见的布局显示比率是 4:3。 对于前置摄像头&#xff0c;图像缓冲区逆时针旋转&#xff08;从 传感器的自然方向&#xff09;对于后置摄像头&#xf…

机械硬盘如何克隆至固态硬盘,如何把硬盘系统克隆到固态硬盘

由于电脑的磁盘空间是既定的&#xff0c;随着使用年限越来越长&#xff0c;电脑磁盘空间已经不满足实际的需求了。为了拓展磁盘空间&#xff0c;就可以使用磁盘克隆的方法来扩展磁盘空间。因此&#xff0c;在本文中&#xff0c;易我小编将讲解硬盘克隆的相关理论知识&#xff0…

非零基础自学Golang 第7章 函数 7.5 匿名函数和闭包

非零基础自学Golang 文章目录非零基础自学Golang第7章 函数7.5 匿名函数和闭包7.5.1 定义和使用匿名函数7.5.2 闭包的定义7.5.3 闭包的“记忆力”第7章 函数 7.5 匿名函数和闭包 匿名函数即在需要函数时定义函数&#xff0c;匿名函数能以变量方式传递&#xff0c;它常常被用于…