模拟实现vector/迭代器失效问题

news2024/11/26 3:54:20

对于STL,我们不仅学会使用STL,还要了解其底层原理,这样一来,我们就能知道什么时候用string好,什么时候用vector,什么时候用list,哪种方法效率高等等。其次了解了STL的底层原理,也助于我们的C++功力大增!

先来看看vector类的成员变量:下图是从《STL源码剖析》书中截取的

vector类的成员变量有三个,分别是iterator start、iterator finish和iterator end_of_storage。我们可以用string类的成员变量来理解这三个变量:

string 类的成员变量有:T* a , size_t  _size , size_t  _capacity

start也就是a,finish也就是a+_size,end_of_storage也就是a+_capacity。

源码中的vector类:

template <class T, class Alloc = alloc>
class vector {
public:
    typedef T value_type;
    typedef value_type* iterator;
    typedef const value_type* const_iterator;
    //......
protected:
    iterator start;
    iterator finish;
    iterator end_of_storage;
    }

开始模拟实现->

为了避免冲突,先创建一个命名空间,然后在命名空间里面创建vector类:

namespace my_vector
{
	template<class T>
	class vector {
	public:
		typedef T* iterator;//迭代器
        typedef const T* const_iterator;//const迭代器


	private:
		iterator _start;
		iterator _finish;
		iterator end_of_storge;

	};
}

1.构造函数:

①无参构造,将成员变量全部置为空即可

		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storge(nullptr)
		{}

②迭代器区间构造:

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storge(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

其它的接口就先实现,方便后续使用:

交换接口:

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

析构函数:

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

清空数据:

		void clear()
		{
			_finish = _start;
		}

③拷贝构造

传统写法:

先初始化,然后开空间,最后是将被拷贝对象的值弄到拷贝对象中。在范围for循环中,最好是引用。

		vector(const vector& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storge(nullptr)
		{
			//先开辟空间
			reserve(v.capacity);
			for (auto& e : v)
			{
				push_back(e);
			}
		}

现代写法:

		vector(const vector& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storge(nullptr)
		{
			vector<T> tmp(v.begin, v.end);//用迭代器区间构造,找个打工人
			swap(tmp);
		}

2.插入数据的相关函数接口:

①reserve()的模拟实现:

因为在插入数据时,不管是最初状态还是空间满的时候,都得扩容,所以就先实现reserve()。因为reserve是不会缩容的,缩容和扩容是需要代价的,而扩容是不可避免的,但是缩容就不必要了,采用空间换时间的策略。

在最初状态,_start是指向空指针的,因此在扩容的时候需要判断一下。

		void reserve(size_t n)//不止是在插入数据的时候使用,也可以单独使用
		{
			if (n > capacity())//因为不缩容,所以判断一下,大的时候才处理
			{
				size_t oldSize = size();
				T* tmp = new T[n];
				if (_start)//判断一下_start是否为空,因为如果一开始的capacity是空的,然后赋值为4,但是此时的_start是nullptr,给不了数据 
				{
					//不建议用memcpy,因为它是浅拷贝,如果T是string、vector等等,就会出错
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < oldSize; i++)
					{
						tmp[i] = _start[i];
					}
					delete _start;
				}
				
				_start = tmp;
				_finish = tmp + oldSize;
				_end_of_storge = _start + n;
			}
		}

②size()和capacity():

实现size()和capacity(),方便操作,并且这也是需要实现的一部分。

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

		size_t capacity() const
		{
			return _end_of_storge - _start;
		}

③push_back():

因为_finish表示的是有效元素的个数,指向的是最后一个元素的下一个位置,因此不管是否需要扩容,尾插的位置必然是_finish指向的位置。

	    void push_back(const T& x)
		{
			if (_finish == _end_of_storge)//判断空间是否满了
			{
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);
			}
			*_finish = x;//直接在_finish这个位置插入数据
			++_finish;

		}

④insert接口/迭代器失效问题

void insert(iterator pos, const T& val);

这部分很重要,因为涉及了迭代器失效问题!我们都知道,在插入数据前,我们需要进行一次判断,判断容器的容量是否满了,如果满了,则需要扩容,而问题也就发生在这里,扩容会导致迭代器失效的问题!(当然,迭代器失效的问题不仅仅会出现在这)

在扩容的时候,是重新开辟一块大的空间,然后释放原来的空间,看下图:

 这样就导致了插入数据失败。解决问题呢,就是更新pos,让pos指向新空间的同一块位置:在扩容前记录一下pos距离_start的长度,在扩容后,让pos重新指向新空间的_start+len处。

		//迭代器失效问题
		void insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			if (_finish == _end_of_storge)
			{
				size_t len = pos - _start;//算出pos距离_start的长度
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);
				//待空间更新,就更新pos的位置,解决pos失效的问题
				pos = _start + len;
			}

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

			*pos = val
			++_finish;
		}

当然,不扩容的话,就可能不会导致失效的问题了。其实迭代器失效,也就是野指针的问题。

解决迭代器哦失效,便是

3.实现迭代器

普通对象迭代器:

刚好,迭代器的begin刚好就是_start,end也刚好是_finish。

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

const迭代器:

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}
		

4.访问接口[]:


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

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

5.判空

当_finish等于_start的时候,说明此时容器是空的,没有空间,没有数据。

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

6.resize()

对于resize(size_t n,T val = T()),是针对有效元素个数的。当n是大于容器当前的容量,则代表着是扩容+插入数据,当n小于容器的当前容量,大于当前有效元素个数,那么代表着不扩容,但是插入数据,当n小于当前容量,那么就是相当于删除数据。

那么插入的数据的话,缺省值是T(),即匿名对象,因为T有可能是string类型,是Date类型,是各种各样的类型,因此需要它的构造函数去初始化。

		void resize(size_t n, T val = T())//T()是匿名对象,初始化resize的空间
		{
			if (n > capacity())//扩容
			{
				reserve(n);
			}

			if (n > size())//不扩容,但是插入数据
			{
				//从_finish开始填数据
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else//删除数据
			{
				_finish = _start + n;//相当于把有效元素的个数减少到n个
			}
		}

7.删除数据的接口:

①pop_back();

		void pop_back()
		{
			assert(empty());//如果容器已经空, 再次调用的话,直接报错
			--_finish;
		}

②erase()接口以及其引起的迭代器失效

删除任意位置,即挪动要删除的数据的后面的位置,将他们往前挪即可。

		void erase(iterator pos)
		{
			//pos的位置要合理
			assert(pos >= _start);
			assert(pos < _finish);

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

		}

删除操作,会让pos迭代器会失效,但注意,在Linux下g++中不会报错,不会失效,因为g++采用的是模拟实现,它做不到识别失效问题,pj版能够识别,是因为它不是一个原生指针。但不管怎么样,一般来说统一认为它会失效!

而解决失效问题,可以将代码改成如下:

		iterator erase(iterator pos)
		{
			//pos的位置要合理
			assert(pos >= _start);
			assert(pos < _finish);

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


		}

删除后,更新pos位置即可。

8.find导致的迭代器失效问题

	my_vector::vector<int>::iterator it = find(arr.begin(), arr.end(), 3);
	if (it != arr.end())
	{
		arr.insert(it, 30);
	}
	//可能发生迭代器失效
	(*it)++;

如上代码,在insert之和,it会发生迭代器失效。其原因是:即使我们在insert后,pos位置更新,使得pos没有失效,但是对于reserve接口,传参是传值传参,pos的改变不会影响it,而it作为参数传到insert接口后,由于原本指向的空间已经释放了,it就变成了野指针,也就是迭代器失效了。当然了,如果没有发生扩容,就可能不会发生失效。

在C++官方库里面,insert也没有引用作参数,也是传值传参的。简单地解释一下就就是,有时候传进去的是一些具有常性的临时变量,不可传,比如insert(arr.begin(),1);,其中的arr.begin()就是临时变量。

9.赋值操作

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

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

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

相关文章

RTF、RIR、Steering Vector傻傻分不清

RTF&#xff1a; Relative transfer function&#xff0c;相对传递函数RIR: Room impulse response&#xff0c;空间冲击响应Steering vector: 导向矢量场景问题定义&#xff1a;空间中存在I个麦克风和J个声源&#xff0c;麦克风采集到的信号其中&#xff0c;麦克i的信号其中表…

一起自学SLAM算法:9.1 ORB-SLAM2算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 下面将从原理分析、源码解读和安装与运行这3个方面展开讲解ORB-SLAM2算法。 9.1.1 ORB-SLAM2原理分析 前面已经说过&#xff0c;ORB-SLAM2算法是特征点法的典型代表。因此在下面的分析中&#xff0c;首先介绍一…

被删库勒索了,怎么使用docker进行MySQL容器的管理?

大家觉得写还可以&#xff0c;可以点赞、收藏、关注一下吧&#xff01; 也可以到我的个人博客参观一下&#xff0c;估计近几年都会一直更新&#xff01;和我做个朋友吧&#xff01;https://motongxue.cn 起因&#xff1a;云服务器MySQL密码设置的太简单了&#xff0c;导致到被入…

路由策略实验

1.先配置IP和环回 [Huawei]sysname R1 [R1]interface GigabitEthernet 0/0/0 [R1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [R1-GigabitEthernet0/0/0]int g 0/0/1 [R1-GigabitEthernet0/0/1]ip add 22.1.1.1 24 [R1-GigabitEthernet0/0/1]q [R1]int l 0 [R1-LoopBack0]ip ad…

ETHDenver 2023 的 Cartesi BUIDLathon 项目创意

希望你在了解Cartesi之前&#xff0c;谨慎对待自己的行为。一旦你开始研究并搜寻可以使用Cartesi Rollups构建的项目或者应用&#xff0c;你就会陷入一个令人兴奋的螺旋洞穴中&#xff0c;你会上瘾。如果你想在2023年中建造一些很具有意义的事情&#xff0c;那你就来对地方了。…

Python01概述 基础语法 判断

Python概述 第二章-Python基础语法 01-字面量 02-注释 03-变量 04-数据类型 05-数据类型转换 06-标识符 07-运算符 08-字符串的三种定义方式 09-字符串的拼接 10-字符串格式化 11-字符串格式化的精度控制 12-字符串格式化的方式-快速写法 13-对表达式进行格式化 14-字符串格…

Java语法核心——面向对象编程

目录 面向过程思想概述 面向对象思想概述 面向对象思想特点及举例 类与对象的关系 类的定义 类与对象的案例(demo02) 对象内存存储机制 成员变量和局部变量的区别 private关键字 面向过程思想概述 我们回想一下&#xff0c;这几天我们完成一个需求的步骤&#xff1a;首…

echarts数据可视化项目搭建(一)

目录直角坐标系通用配置项tooltiptoolboxlegenddataZoom柱状图常见效果折线图常见效果散点图常见效果其他坐标系饼图基本实现常见效果地图地图基本展示不同城市颜色不同地图与散点图结合雷达图仪表盘本博客内容参考黑马课程&#xff0c;详细信息请参考以下网址 Bilibili官方黑…

Apache Superset 开源商业智能大数据可视化

Apache Superset 是一款现代化的开源大数据工具&#xff0c;也是企业级商业智能 Web 应用&#xff0c;用于数据探索分析和数据可视化。 Apache Superset 是一个适合企业日常生产环境中使用的商业智能可视化工具。它具有快速、轻量、直观的特点&#xff0c;任何用户都可以轻松地…

Spring Boot学习之Shiro

文章目录零 全部源码地址一 Shiro简介1.1 Shiro功能1.2 Shiro架构&#xff08;外部视角&#xff09;1.3 Shiro架构&#xff08;内部视角&#xff09;二 Shiro快速入门2.1 演示代码&部分源码解读三 Spring Boot集成Shio3.0 准备操作3.1 整合Shiro3.2 页面拦截实现3.3 登录认…

ESP32设备驱动-HMC5983磁力计驱动

HMC5983磁力计驱动 1、HMC5983介绍 霍尼韦尔 HMC5983 是一款温度补偿型三轴集成电路磁力计。这种表面贴装、多芯片模块专为汽车和个人导航、车辆检测和指向等应用的低场磁场传感而设计。 HMC5983 包括我们最先进的高分辨率 HMC118X 系列磁阻传感器和一个 ASIC,该 ASIC 包含…

AOP切面编程

前言&#xff1a;AOP&#xff08;Aspect Oriented Programming&#xff09;是一种设计思想&#xff0c;是软件设计领域中的面向切面编程&#xff0c;它是面向对象编程的一种补充和完善&#xff0c;它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态…

各种Sequence Self-Attention变形 (加速矩阵运算 且保证全局特征)

人工设计Self-attention的N*N矩阵1. Local Attention/Truncated Attention2. Stride Attention3. Global Attention人工设计Self Attention的使用与选择1.LongFomer2.Big Bird自动设计Self Attention的N*N矩阵1. Reformer2.Sinkborn Sorting Network不需要N*N大小的矩阵1.Linfo…

【python】图片转字符画 cv2+pygame实现

网上看到一些字符画,非常羡慕,想要用python写一个类似的东西,突然想到字符画不就是把图片分割为像素块再进行替换嘛 恰好之前稍稍入门了python的opencv库,可以对图片进行处理。 处理图片的思想为:对一个区域的像素进行参考值计算,用具有相似参考值的字符进行替代,因此除…

打工人必学的法律知识(七)——《中华人民共和国劳动合同法实施条例》

目录 来源 第一章 总 则 第二章 劳动合同的订立 第三章 劳动合同的解除和终止 第四章 劳务派遣特别规定 第五章 法津责任 第六章 附 则 来源 《中华人民共和国劳动合同法实施条例》 第一章 总 则 第一条 为了贯彻实施《中华人民共和国劳动合同法》&#xff08;以下简称…

mybatis说明

目录 1.说明 2.配置文件 3.映射器 4.select标签 5.insert标签 6.update标签 7.delete标签 8.resultMap的特别说明 9.注解 10.关联(级联)查询 11.动态sql 12.mybatis分页 13缓存 1.说明 MyBatis 是一个开源、轻量级的数据持久化框架&#xff0c;是 JDBC 和 Hiberna…

Redis学习笔记:数据结构和命令

本文是自己的学习笔记。主要参考资料如下&#xff1a; 马士兵 4、Redis的五大数据类型1.1、String1.1.1、String 类型的命令1.1.2、存储对象1.2、List1.2.1、List基本命令1.2.2、List高级命令1.3、Set1.3.1、Set基本命令1.4、HashMap1.4.1、HashMap基本命令1.5、ZSet&#xff0…

【数据结构】7.4 散列表的查找

文章目录7.4.1 散列表的基本概念7.4.2 散列函数的构造散列函数的构造方法7.4.3 处理冲突的方法1. 开地址法1.1 线性探测法1.2 二次探测法2. 链地址法7.4.4 散列表的查找散列表的查找效率分析总结7.4.1 散列表的基本概念 基本思想&#xff1a;根据要存储的关键字的值&#xff0…

计算机网络-杂项

目录 1、蜂窝移动网络 2、TCP和UDP 3、5层架构 4、在浏览器中输入url地址显示主页的过程 5、TCP的基本操作 6、三次握手&#xff0c;四次挥手 6.1、三次握手&#xff1a;双方保证自己和对方都能接收和发送数据。 6.2、三次握手中&#xff0c;为什么客户机最后还要再向服…

【计算机网络】应用层体系

我们知道现代常用的计算机网络模型为5层模型&#xff0c;其中应用层是直接与我们平时常见的软件对接的最高层&#xff0c;所以先来学习应用层就显得很有必要了。其中在应用层我们需要学习网络应用程序的实现、原理并且了解网络应用程序所需要的网络服务、客户和服务器、进程和运…