vector迭代器失效与深浅拷贝问题

news2024/11/18 13:35:09

目录

1、vector迭代器失效问题

1.1、insert迭代器失效

扩容导致野指针 

意义变了 

官方库windows下VS和linux下对insert迭代器失效的处理

1.2、erase迭代器失效

官方库windows下VS和linux下对erase迭代器失效的处理

1.3、迭代器失效总结

2、深浅拷贝问题 


1、vector迭代器失效问题

1.1、insert迭代器失效

insert迭代器失效分为两类:

  • 扩容导致野指针
  • 意义变了

下面我们先给出insert的初始版本,然后再逐渐的完善:

void insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	//检测是否需要扩容
	if (_finish == _endofstoage)
	{
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
}

扩容导致野指针 

我们给出以下两组测试用例:

这里为什么push_back尾插4个后调用insert会出现随机值?而push_back尾插5个调用insert就没有这个问题?

此问题就是迭代器失效,原因就在于pos没有更新,导致非法访问野指针。

 

上述当尾插4个数字后,再头插一个数字发生扩容。根据reserve扩容机制,_start和_finish都会更新,唯独插入位置pos没有更新,此时pos依旧指向旧空间,reserve后会释放旧空间,此时的pos就是野指针,这也就导致后续执行*pos=x就是非法访问野指针。

  • 解决方法

我们可以设定变量n来计算扩容前pos指针位置和_start指针位置的相对距离,最后在扩容后,让_start再加上先前算好的相对距离n就是更新后的pos指针的位置了。

  • 修正如下
void insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	/*扩容以后pos就失效了,需要更新一下*/
	if (_finish == _endofstoage)
	{
		size_t n = pos - _start;//计算pos和start的相对距离
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
		pos = _start + n;//防止迭代器失效,要让pos始终指向与_start间距n的位置
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
}


意义变了 

比如现在我要在所有的偶数前面插入2,下面我们看测试结果:

这里发生了断言错误,这段代码发生了两个错误:

  1. it是指向原空间的,当insert插入要扩容时,原空间的数据拷贝到新空间上,但这也就意味着旧空间全是野指针,而it是一直指向旧空间的,随后遍历it时就非法访问野指针,也就失效了。形参的改变不会影响实参,即使你内部pos指向改变了,但是并不会影响我外部的it。
  2. 为了解决上述问题,有人觉得提前reserve开辟足够大的空间即可避免发生野指针的现象,但是又会出现一个新的问题,我们看下图:

 此时insert以后虽然没有扩容,it也并没有成为野指针,但是it指向位置意义变了,导致我们这个程序重复插入20。

  • 解决方法:

给insert函数加上返回值即可解决,返回指向新插入元素的位置。

iterator insert(iterator pos, const T& x)
{
	//检测参数合法性
	assert(pos >= _start && pos <= _finish);
	//检测是否需要扩容
	/*扩容以后pos就失效了,需要更新一下*/
	if (_finish == _endofstoage)
	{
		size_t n = pos - _start;//计算pos和start的相对距离
		size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapcacity);
		pos = _start + n;//防止迭代器失效,要让pos始终指向与_start间距n的位置
	}
	//挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	//把值插进去
	*pos = x;
	_finish++;
	return pos;
}

我们实际调用的时候也需要改动,让it自己接收insert后的返回值:


官方库windows下VS和linux下对insert迭代器失效的处理

VS:针对于扩容发生野指针类的迭代器失效,VS官方库是直接断言报错。

Linux:linux下可以直接进行访问,甚至是可以进行修改。


1.2、erase迭代器失效

我们给出erase模拟实现的代码:

iterator erase(iterator pos)
{
	//检查合法性
	assert(pos >= _start && pos < _finish);
	//从pos + 1的位置开始往前覆盖,即可完成删除pos位置的值
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;		
        it++;
	}
	_finish--;
	return pos;
}
  • erase的失效都是意义变了,或者不在有效访问数据的有效范围内
  • erase一般不会使用缩容的方案,那么也就导致erase的失效一般不存在野指针的失效。

我们现在对下面代码进行测试:

void test_vector()
{
	Fan::vector<int> v;
	//v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	cout << v.size() << ":" << v.capacity() << endl;
	auto pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.erase(pos);
	}
	cout << *pos << endl;
	*pos = 10;
	cout << *pos << endl << endl;
	cout << v.size() << ":" << v.capacity() << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
}

我们尾插4个数字,比较size和capacity的大小,此时是相等的,接下来删除值为2的数,此时*pos就是删除数字的下一个数据也就是3,此时有效数据也少了一个,后续修改*pos也就不存在问题。 

  • 如果我们这里要删除的值为4,那么结果会是怎么样的呢?

我们这里一共有4个数据,按理说把最后一个数字删除以后,有效数字-1,不存在还会访问最后一个值的现象,但是此结果却是删除4以后又访问了4,而且还修改4为10,这就是典型的erase迭代器失效。 


官方库windows下VS和linux下对erase迭代器失效的处理

VS:VS检查环境十分的严格,直接强制检查断言错误。

Linux:Linux下对于迭代器失效的检查宽泛很多,并不会报错。

  • 结论 
  1. erase(pos)以后pos失效了,pos的意义变了,但是在不同平台下面对于访问pos的反应是不一样的,我们用的时候要以失效的角度去看待此问题。
  2. 对于insert和erase造成迭代器失效问题,linux的g++平台检查很佛系,基本靠操作系统本身野指针越界检查机制。windows下VS系列检查更严格一些,使用一些强制检查机制,意义变了可能会检查出来。
  3. 虽然g++对于迭代器失效检查时是非常佛系的,但是套在实际场景中,迭代器意义变了,也会出现各种问题。

下面我们再给出一组测试用例:

void test_vector()
{
	//删除所有的偶数
	vector<int> v;
	//v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
		}
		it++;
	}
	for (auto e : v)
	{
		cout << e << " ";
	}

}

代码直接崩溃。

  • 画图演示错误过程: 
  •  解决方案如下:
void test_vector()
{
	//删除所有的偶数
	std::vector<int> v;
	//v.reserve(10);
	v.push_back(1);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it = v.erase(it);
		}
		else
		{
			it++;
		}
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
}

1.3、迭代器失效总结

vector迭代器失效有两种

  • 扩容、缩容导致野指针式失效
  • 迭代器指向的位置意义变了

系统越界机制检查,不一定能够检查到。编译实现检查机制,相对比较靠谱。


2、深浅拷贝问题 

我们用先前模拟实现的vector来测试杨辉三角以此来解释深浅拷贝问题:

namespace Fan
{
	class Solution {
	public:
		// 核心思想:找出杨辉三角的规律,发现每一行头尾都是1,中间第[j]个数等于上一行[j-1]+[j]
		vector<vector<int>> generate(int numRows) {
			vector<vector<int>> vv;
			// 先开辟杨辉三角的空间
			vv.resize(numRows);
			for (size_t i = 1; i <= numRows; ++i)
			{
				vv[i - 1].resize(i, 0);
				// 每一行的第一个和最后一个都是1
				vv[i - 1][0] = 1;
				vv[i - 1][i - 1] = 1;
			}
			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
					}
				}
			}
			return vv;
		}
	};

	void test()
	{
		vector<vector<int>> vv = Solution().generate(5);
		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;
		}
	}
}

理想结果如下:

测试结果如下: 

我们把初版扩容代码给出:

//reserve扩容
void reserve(size_t n)
{
	size_t sz = size();//提前算出size()的大小,方便后续更新_finish
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)//判断旧空间是否有数据
		{
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;//释放旧空间
		}
		_start = tmp;//指向新空间
	}
	//更新_finish和_endofstoage
	_finish = _start + sz;
	_endofstoage = _start + n;
}
  • 分析如下:

我们仔细看上面,我们调用类Solution返回对象vv的临时拷贝,然后将临时拷贝对象赋值给test函数里面的vv,这里就涉及类对象的拷贝构造,我们看一下拷贝构造函数:

拷贝构造函数这里是创建了新的对象tmp,然后拿参数v的值将其初始化,所以这里涉及类的构造函数,下面我们看一下构造函数:

我们能够看到的是构造函数是把传过来的数据一个个尾插到容器里面去,问题就出在了这里的push_back上面。因为当我们要插入第五个数据的时候是需要扩容的:

 

总结:

  1. vector<T>中,当T涉及深浅拷贝的类型时,如:string/vector<T>等等,我们扩容使用memcpy拷贝数据是存在浅拷贝问题。
  2. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。
  3. 如果拷贝的是自定义类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

解决方案:

reserve扩容时不使用memcpy,我们改用for循环来解决:

//reserve扩容
void reserve(size_t n)
{
	size_t sz = size();//提前算出size()的大小,方便后续更新_finish
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)//判断旧空间是否有数据
		{
			//不能用memcpy,因为memcpy是浅拷贝
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;//释放旧空间
		}
		_start = tmp;//指向新空间
	}
	//更新_finish和_endofstoage
	_finish = _start + sz;
	_endofstoage = _start + n;
}

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

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

相关文章

GeoServer发布数据进阶

GeoServer发布数据进阶 GeoServer介绍 GeoServer是用于共享地理空间数据的开源服务器。 它专为交互操作性而设计&#xff0c;使用开放标准发布来自任何主要空间数据源的数据。 GeoServer实现了行业标准的 OGC 协议&#xff0c;例如网络要素服务 &#xff08;WFS&#xff09;…

Java【优先级队列】模拟实现 + 【PriorityQueue】介绍

文章目录一、什么是优先级队列二、模拟实现1, 实现堆的基本操作1.1, 创建堆1.2.1, 向下调整1.2, 堆的插入1.2.1, 向上调整1.2, 堆的删除2, 实现优先级队列2.1, offer -- 插入数据2.1, poll -- 删除数据三、Java提供的PriorityQueue1, PriorityQueue说明2, 使用PriorityQueue2.1…

【Linux】安装Tomcat教程

目录 1.上传安装包 2.解压安装包 3.启动Tomcat 4.查看启动日志 5.查看进程 6.开放端口 7.停止Tomcat 1.上传安装包 使用FinalShell自带的上传工具将Tomcat的二进制发布包上传到Linux(与前面上传JDK安装包步骤 一致)。 2.解压安装包 将上传上来的安装包解压到指定目录…

2023年想跳槽,什么类型的人才需求最多?

某招聘网站资深HR对此表示&#xff1a;纵观当前招聘市场&#xff0c;无论是比较火爆的互联网行业还是传统行业&#xff0c;技能型人才都是最受欢迎的人才之一&#xff1b;那些拥有职场一技之能的跳槽者往往跳的结果更好&#xff0c;包括薪酬水平和发展空间、重视程度等。 那选择…

一个更适合Java初学者的轻量级开发工具:BlueJ

Java是世界上最流行的编程语言之一&#xff0c;它被广泛用于从Web开发到移动应用的各种应用程序。大部分Java工程师主要是用IDEA、Eclipse为主&#xff0c;这两个开发工具由于有强大的能力&#xff0c;所以复杂度上就更高一些。如果您刚刚开始使用Java&#xff0c;或者您更适合…

如何简化跨网络安全域的文件发送流程,大幅降低IT人员工作量?

为什么要做安全域的隔离&#xff1f; 随着企业数字化转型的逐步深入&#xff0c;企业投入了大量资源进行信息系统建设&#xff0c;信息化程度日益提升。在这一过程中&#xff0c;企业也越来越重视核心数据资产的保护&#xff0c;数据资产的安全防护成为企业面临的重大挑战。 …

自动化测试学习步骤及路线(超详细)

随着测试行业的不断发展&#xff0c;目前企业对测试人员要求越来越高&#xff0c;仅仅响应需求的功能测试人员基本饱和或是留给了校招生。而对于社招渠道的应聘者&#xff0c;企业越来越多地要求有一定的自动化或是代码经验&#xff0c;能解决工作过程中遇到的问题&#xff0c;…

ledcode【用队列实现栈】

目录 题目描述&#xff1a; 解析题目 代码解析 1.封装一个队列 1.2封装带两个队列的结构体 1.3封装指向队列的结构体 1.4入栈函数实现 1.5出栈函数实现 1.6取栈顶数据 1.7判空函数实现 题目描述&#xff1a; 解析题目 这个题我是用c语言写的&#xff0c;所以队列的pu…

魔兽世界WoW注册网站搭建——-Liunx

问题背景哎 搭建了一个魔兽3.35&#xff08;纯洁版&#xff09;每当同学朋友要玩的时候我都直接worldserver上面打一个命令随之出现朋友的朋友也要玩想了想还是要有一个网站原本以为吧单机版里面网页的IP数据库改下可以了结果PHP报错了Unknown column sha_pass_hash in field l…

学校AI视频行为分析监测系统 opencv

学校AI视频行为分析监测系统通过pythonopencv网络模型AI视频分析技术&#xff0c;学校AI视频行为分析监测算法对学校区域人员打架行为识别、跌倒行为识别、翻墙识别、人员聚众识别、攀高识别、抽烟行为等进行智能识别预警。OpenCV的全称是Open Source Computer Vision Library&…

C#专栏目录(长期更新)

文章目录C# 基础C#进阶C#应用WPF基础WPF 3D小游戏C# 基础 1996年&#xff0c;微软用年薪三百万美刀的价格从Borland挖来了大神海尔斯伯格&#xff0c;开始了J开发&#xff0c;用以对抗Java。但SUN公司认为此举违反了Java开发平台的中立性&#xff0c;对微软提出诉讼。C#正是在…

RBAC 权限模型介绍

RBAC 权限&#xff1a; 一、关系&#xff1a; 这基于角色的访问控制的结构就叫RBAC结构。 二、RBAC 重要对象&#xff1a; 用户&#xff08;Employee&#xff09;&#xff1a;角色施加的主体&#xff1b;用户通过拥有某个或多个角色以得到对应的权限。角色&#xff08;Role&…

SQL零基础入门学习(十三)

上一篇&#xff08;SQL零基础入门学习&#xff08;十二&#xff09;&#xff09; SQL 视图&#xff08;Views&#xff09; 视图是可视化的表。 SQL CREATE VIEW 语句 在 SQL 中&#xff0c;视图是基于 SQL 语句的结果集的可视化的表。 视图包含行和列&#xff0c;就像一个…

jsp+servlet+javabean新闻发布系统

技术&#xff1a;Java、JSP等摘要&#xff1a;近年来,Internet技术得到迅速的发展,已经成为计算机产业的一个技术热点。促成Internet高速发展的因素之一就是Web技术。Web技术的发展使得那些具有交互动态页面、有条理的数据库查询、丰富信息内容的页面成为最吸引人的网页。浏览W…

MVCC 当前读 快照读 RC read view RR下事务更新不会丢失

MVCC(multi-version-concurrent-control) MVCC是行锁的一个变种&#xff0c;但MVCC在很多情况下它避免了加锁。不是buffer块&#xff0c;而是buffer中的记录行。 MVCC (Multi-Version Concurrency Control) (注&#xff1a;与MVCC相对的&#xff0c;是基于锁的并发控制&#x…

SVIP优先办理服务-课后程序(JAVA基础案例教程-黑马程序员编著-第八章-课后作业)

【案例8-2】 Svip优先办理服务 【案例介绍】 1.任务描述 在日常工作生活中&#xff0c;无论哪个行业都会设置一些Svip用户&#xff0c;Svip用户具有超级优先权&#xff0c;在办理业务时&#xff0c;Svip用户具有最大的优先级。 本案例要求编写一个模拟Svip优先办理业务的程…

350-401-拖图题1

拖图题 QoS&#xff1b;policing&#xff1a;dropped,no delay;shaping:buffers,delay&#xff1b;policing有TCP和no。shaping有buffer缓冲器和delay延迟&#xff1b;警察安全不丢失&#xff0c;定型过多延迟又延迟 traffic policing&#xff1a;流量监管 causes TCP retran…

JUnit介绍与使用

自动化是通过selenium脚本来实现的&#xff0c;而JUnit是Java单元测试工具&#xff0c;只不过我们在实现自动化的时候要借用一些JUnit库里提供的一些方法。优化我们的自动化使用版本&#xff1a;JUnit5&#xff08;支持最低Java版本为8&#xff09;依赖导入到pom.xml&#xff1…

浅谈权限获取方法之文件上传

概述 文件上传漏洞是发生在有上传功能的应用中&#xff0c;如果应用程序对用户的上传文件没有控制或者存在缺陷&#xff0c;攻击者可以利用应用上传功能存在的缺陷&#xff0c;上传木马、病毒等有危害的文件到服务器上面&#xff0c;控制服务器。 漏洞成因及危害 文件上传漏…

如何进行域名购买,获取免费ssl证书,使用springboot绑定ssl证书

前言 小编我将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识&#xff0c;有兴趣的小伙伴可以关注一下&#xff01;也许一个人独行&#xff0c;可以走的很快&#xff0c;但是一群人结伴而行&#xff0c;才能走的更远&#xff01;让我们在成长的道路上互相学习&#…