vector模拟实现【C++】

news2024/11/20 6:18:00

文章目录

  • 全部的实现代码放在了文章末尾
  • 准备工作
    • 包含头文件
    • 定义命名空间和类
      • 类的成员变量
  • 迭代器
    • 迭代器获取函数
  • 构造函数
    • 默认构造
    • 使用n个值构造
    • 迭代器区间构造
    • 解决迭代器区间构造和用n个值构造的冲突
    • 拷贝构造
  • 析构函数
  • swap【交换函数】
  • 赋值运算符重载
  • empty
  • size和capacity
  • operator[]
  • reserve【调整容量大小】
  • resize【调整size大小】
  • push_back
  • assign【把所有数据替换成迭代器区间中的数据】
  • insert
    • 为什么扩容会导致pos迭代器失效?
    • 为什么要返回pos-1?
  • erase
    • 为什么要返回pos?
  • 全部代码

全部的实现代码放在了文章末尾

准备工作

创建两个文件,一个头文件myvector.hpp,一个源文件 tesr.cpp
【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件myvector.hpp中】

  1. mystring.h:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义

  2. test.cpp:存放main函数,以及测试代码


包含头文件

  1. iostream:用于输入输出

  2. assert.h:用于使用报错函数assert


定义命名空间和类

在文件myvector.hpp中定义上一个命名空间myvector
把vector类和它的成员函数放进命名空间封装起来,防止与包含的头文件中的函数/变量重名的冲突问题

类的成员变量

参考了stl源码中的vector的实现
成员变量有3个,都是迭代器

在这里插入图片描述
画图理解一下
在这里插入图片描述


迭代器

迭代器
因为存放数据的空间是从堆区申请的连续的内存,且只是简单模拟

所以我用了指针T*作为普通迭代器,const T*作为const迭代器
T是vector中存储的数据的类型

直接把T*重命名为iterator,把const T*重命名为const_iterator就完成了迭代器的实现

在这里插入图片描述

迭代器获取函数

在这里插入图片描述

因为const修饰的对象只能调用const修饰的成员函数
所以如果是const修饰的对象调用begin()和end()的时候,就会自动调用到const修饰的begin和end.


构造函数

默认构造

因为stl库里实现的默认构造是没开空间的,所以默认构造直接让3个成员变量都为nullptr就行

直接在声明的时候给缺省值,缺省值会传给成员初始化列表

在这里插入图片描述

而成员初始化列表会比构造函数先调用
并且每个构造函数调用之前都会先调用成员初始化列表,这样不管调用哪一个构造函数初始化,都会先把3个成员变量初始化成nullptr


使用n个值构造

在这里插入图片描述


迭代器区间构造

在这里插入图片描述


解决迭代器区间构造和用n个值构造的冲突

当重载了迭代器区间构造和用n个值构造的时候
如果传入的两个参数都是int类型的话就会报错

为什么?
因为在模板函数构成重载时,编译器会调用更合适的那一个
什么叫更合适?
就是不会类型转

如果传入的两个参数都是int类型,那么调用的应该是使用n个值构造,因为没有int类型的迭代器

但是使用n个值构造的第一个参数是size_t,int传进去要隐式类型转换
而调用迭代器区间构造,两个int的实参传进去,就会直接把InputIterator推导成int,不会发生类型转换,所以编译器会调用迭代器区间构造

解决方法:
再重载一个使用n个值构造的函数,把第一个参数改成int
在这里插入图片描述


拷贝构造

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】
在这里插入图片描述


析构函数

在这里插入图片描述


swap【交换函数】

因为存放数据的空间是在堆区开辟的,用3个成员变量去指向的

所以直接交换两个对象的成员变量就可以了

不需要拷贝数据

在这里插入图片描述


赋值运算符重载

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】

在这里插入图片描述
为什么上面的两句代码就可以完成深拷贝呢?
这是因为:

使用了传值传参,会在传参之前调用拷贝构造,再把拷贝构造出的临时对象作为参数传递进去

赋值运算符的左操作数,*this再与传入的临时对象obj交换,就直接完成了拷贝

在函数结束之后,存储在栈区的obj再函数结束之后,obj生命周期结束

obj调用析构函数,把指向的从*this那里交换来的不需要的空间销毁


empty

在这里插入图片描述


size和capacity

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述


operator[]

在这里插入图片描述
因为
const修饰的对象只能调用const修饰的成员函数

所以const对象只会调用下面的那个重载


reserve【调整容量大小】

在这里插入图片描述


resize【调整size大小】

在这里插入图片描述


push_back

在这里插入图片描述


assign【把所有数据替换成迭代器区间中的数据】

在这里插入图片描述


insert

	iterator insert(iterator pos, const T& val)
	{
		assert(pos <= _finish);  防止插入的位置是 越界的

		if (_finish == _end_of_storage)  如果容量满了
		{
			记录一下扩容前的pos与start的相对位置
			因为扩容的话会导致pos迭代器失效
			size_t n = pos-_start;

			if (capacity() == 0)  如果容量为0
				reserve(2);
			else  容量不为0,就扩2reserve(capacity() * 2);

			更新pos
			pos = _start + n;
		}
		iterator it = end()-1;

		把pos及其之后的数据向后挪动一位
		while (it >= pos)
		{
			*(it + 1) = *it;
			it--;
		}
		_finish++;

		*pos = val;插入数据

        返回指向新插入的数据的迭代器  
        用于处理迭代器失效问题
		return pos-1;
	}

为什么扩容会导致pos迭代器失效?

在这里插入图片描述
因为扩容之后原来的空间被释放了
又因为使用的扩容方式是reserve所以那3个成员变量的值扩容后可以指向正确的位置。
但是pos如果不更新的话,就还是指向被释放的空间,就成了野指针了。

更新方法也很简单,保存扩容之前的pos与start的相对距离n,扩容之后再让pos=_start+n就可以了。

为什么要返回pos-1?

这是stl库里面处理迭代器失效的方法之一
因为我们在使用stl库里面的insert函数的时候,是不知道什么时候会扩容的【每个平台实现的vector是不同的
只能默认使用了之后传进去pos,在调用一次insert之后就失效了,失效的迭代器是不能使用的。
所以如果还要用pos就要把它更新一下,stl库里提供的更新方式就是:
==让pos接收insert的返回值。【pos是传值调用,形参改变不影响实参】==并且规定insert的返回值要是指向新插入的数据的迭代器


erase

在这里插入图片描述

为什么要返回pos?

因为使用了erase之后的迭代器也会失效,需要提供更新的方法

为什么使用了erase之后的迭代器会失效?

  1. 不确定是否删除到一定数据时,会不会减小容量,以适应size
    此时和insert的一样,因为不能部分释放,所以会把原来的空间释放掉,申请新空间
  2. 不确定是否删除的是最后一个数据,如果是那么调用完erase之后pos指向的就
    不是vector的有效数据范围了

所以和insert一样,调用了erase之后如果还要使用pos,就要接收返回值。

stl库里面规定erase的返回值是指向删除数据的下一个数据的迭代器,因为挪动覆盖的原因,下一个数据就是pos指向的数据,所以返回pos【没有接收返回值的迭代器,在检测较严格的编译器中,不管指向的位置是否正确,都会禁止使用,使用了就报错


全部代码

#include<iostream>
#include<assert.h>

using namespace std;

namespace myvector
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;

		typedef const T* const_iterator;


		vector()
		{
		}

		vector(size_t n,const T& val=T())
		{
			_start = new T[n];//从堆区可容纳申请n个元素大小的空间

			_finish = _start;//还没有 有效数据 时start与finish重合

			_end_of_storage = _start + n;//指向最大容量 的 下一个位置

			for (size_t i = 0; i < n; i++)//循环n次
			{
				push_back(val);//把数据尾插进去
			}
		}

		vector(int n, const T& val = T())
		{
			_start = new T[n];

			_finish = _start;

			_end_of_storage = _start + n;

			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}


		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			//使用迭代器进行循环
			while (first != last)
			{
				push_back(*first);//把数据尾插进去

				++first;
			}
		}

	
		void swap(vector<T>& obj)
		{
			//使用库里面的swap交换3个成员变量
			std::swap(_start, obj._start);

			std::swap(_finish, obj._finish);

			std::swap(_end_of_storage, obj._end_of_storage);
		}

		vector(const vector<T>& obj)
		{
			size_t size = obj.size();//记录有效数据个数
			size_t capacity = obj.capacity();//记录容量大小

			_start = new T[capacity];//申请与obj相同大小的空间

			_end_of_storage = _start + capacity;//指向最大容量 的 下一个位置

			for (size_t i = 0; i < size; i++)//循环size次
			{
				_start[i] = obj._start[i];//把有效数据拷贝过去
			}
			_finish = _start + size;//指向最后一个有效数据的  下一个 位置
		}


		~vector()
		{
			//释放从堆区申请的空间
			delete[] _start;
			 
			//把3个成员变量  置空
			_start = nullptr;
			_finish = nullptr;
			_end_of_storage = nullptr;
		}


		vector<T>& operator= (vector<T> obj)
		{
			swap(obj);

			return *this;
		}


		bool empty() const
		{
			//如果size等于0,就是空的
			return size() == 0;
		}

		size_t size()const
		{
			//finish指向最后一个有效数据的 下一个
			//start指向第一个有效数据
			//两个指针相减就是  两个指针之间的  数据个数
			return _finish - _start;
		}
		size_t capacity()const
		{
			//end_of_storage指向最大容量的 下一个位置
			//start指向第一个有效数据
			//两个指针相减就是  两个指针之间的  数据个数
			return _end_of_storage - _start;
		}

		
		T& operator[] (size_t n)
		{
			//防止越界访问
			assert(n < size());

			//因为start是T*类型,所以可以像数组一样直接随机访问
			return _start[n];
		}


		const T& operator[] (size_t n) const
		{
			//防止越界访问
			assert(n < size());


			//因为start是T*类型,所以可以像数组一样直接随机访问
			return _start[n];
		}


		iterator begin()//普通起始迭代器
		{
			return _start;
		}

		iterator end()// 普通结束迭代器
		{
			return _finish;
		}

		const_iterator begin()const//const起始迭代器
		{
			return _start;
		}

		const_iterator end()const//const结束迭代器
		{
			return _finish;
		}


		void reserve(size_t n)
		{
			if (n > capacity())//要调整的容量n,大于capacity才扩容
			{
				size_t origsize = size();//记录扩容前的size

				T* tmp = new T[n];//申请空间

				//把原来的数据拷贝到  新空间
				for (size_t i = 0; i <origsize; i++)
				{
					tmp[i] = _start[i];
				}

				delete[] _start;//释放旧空间

				//让成员变量指向  新的空间的相对位置
				_start = tmp;
				_finish = _start + origsize;
				_end_of_storage = _start + n;
			}
		}


		void resize(size_t n, const T& val = T())
		{
			if (size() == n)//如果size与要调整的n相等
				return;//直接返回

			else if (size() < n)//如果size小于n
			{
				if (n > capacity())//如果n大于capacity
				{
					reserve(n);//把容量调整到n
				}

				//再把size到n 之间的空间用  val填上
				for (size_t i = size(); i < n; i++)
				{
					push_back(val);
				}
			}
			else//如果size  大于  n
			{
				//就调整标识有效数据的末尾的finish
				//让size=_finish - _start = n
				_finish = _start + n;
			}
		}


		void push_back(const T&val)
		{
			if (_end_of_storage == nullptr)//如果容量为0
			{
				reserve(2);//把容量调整到可容纳 2个元素大小
			}

			else if (_finish==_end_of_storage)//容量满了
			{
				reserve(capacity()*2);//扩容
			}
			//在下标为size【最后一个有效数据的下一个】
			//插入值
			_start[size()] = val;
		
			_finish++;//更新有效数据的末尾
		}


		void pop_back()
		{
			assret(size() > 0);
			_finish--;
		}

		template <class InputIterator>
		void assign(InputIterator first, InputIterator last)
		{
			delete[] _start;//释放原来申请的空间

			//把3个成员变量置空
			_start = nullptr;
			_finish = nullptr;
			_end_of_storage = nullptr;

			while (first != last)
			{
				//一个一个尾插进去
				push_back(*first);

				++first;
			}
		}

		iterator insert(iterator pos, const T& val)
		{
			assert(pos <= _finish);//防止插入的位置是 越界的

			if (_finish == _end_of_storage)//如果容量满了
			{
				//记录一下扩容前的pos与start的相对位置
				//因为扩容的话会导致pos迭代器失效
				size_t n = pos-_start;

				if (capacity() == 0)//如果容量为0
					reserve(2);
				else//容量不为0,就扩2倍
				    reserve(capacity() * 2);

				//更新pos
				pos = _start + n;
			}
			iterator it = end()-1;

			//把pos及其之后的数据向后挪动一位
			while (it >= pos)
			{
				*(it + 1) = *it;
				it--;
			}
			_finish++;

			*pos = val;//插入数据

			return pos-1;
		}
		template <class InputIterator>
		void insert(iterator pos, InputIterator first, InputIterator last)
		{
			assert(pos <= _finish);

			size_t len = 0;
			InputIterator in = first;
			while (in != last)
			{
				in++;
				len++;
			}
			if (_finish + len >= _end_of_storage)
			{
				size_t n = pos - _start;

				if (capacity() == 0)
				{
					reserve(len);
				}
				reserve(capacity()+len);
				pos = _start + n;
			}
			iterator it = end() - 1;
			while (it >= pos)
			{
				*(it + len) = *it;
				it--;
			}
			_finish+=len;

			it = pos;
			while (it != pos + len)
			{
				*it = *first;
				it++;
				first++;
			}
		}


		iterator erase(iterator pos)
		{
			// 防止传入的pos 是越界的
			assert(pos < _finish);

			iterator it = pos;

			//把pos之后的数据都向前挪动一位,把pos指向的位置给覆盖掉
			while (it <end()-1)
			{
				*it = *(it + 1);
				it++;
			}

			//更新数据末尾 迭代器
			_finish--;

			//返回pos
			return pos;
		}


		iterator erase(iterator first, iterator last)
		{
			assert(first >= begin());
			assert(last <= end());
			iterator fi = first;
			iterator la = last;
			size_t len = last - first;
			while (la != end())
			{
				*fi = *la;
				fi++;
				la++;
			}
			_finish -= len;
			return first;
		}
	private:
		//start指向从堆区申请的空间的  起始  位置,与begin()返回的迭代器相等
		//标识有效数组的开始
		iterator _start = nullptr;

		//finish指向  最后一个有效数据的  下一个位置,与end()返回的迭代器相等
		//标识有效数据的结束
		iterator _finish = nullptr;

		//_end_of_storage指向从堆区申请的空间的 末尾的 下一个位置
		//标识容量
		iterator _end_of_storage = nullptr;
	};
}

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

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

相关文章

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…

Java---Mybatis详解二

雄鹰展翅凌空飞&#xff0c; 大江奔流不回头。 壮志未酬心未老&#xff0c; 豪情万丈任遨游。 巍巍高山攀顶峰&#xff0c; 滔滔黄河入海流。 风云变幻凭君舞&#xff0c; 踏遍天涯尽逍遥。 目录 一&#xff0c;环境准备 二&#xff0c;删除 三&#xff0c;删除(预编译SQL) 为什…

[译]全栈Redux实战

本文乱译自一篇英文博文&#xff08;Full-Stack Redux Tutorial&#xff09;&#xff0c;本人英语能力不足&#xff0c;技术能力有限&#xff0c;如有错误&#xff0c;多多包涵。 #关于ReduxReactImmutable的测试先行开发综合指南 Redux是最近发生在js界令人兴奋的事儿。它把…

Elasticsearch 第四期:搜索和过滤

序 2024年4月&#xff0c;小组计算建设标签平台&#xff0c;使用ES等工具建了一个demo&#xff0c;由于领导变动关系&#xff0c;项目基本夭折。其实这两年也陆陆续续接触和使用过ES&#xff0c;两年前也看过ES的官网&#xff0c;当时刚毕业半年多&#xff0c;由于历史局限性导…

耗材分类功能解析:智慧校园的必备利器

在智慧校园的资产管理架构中&#xff0c;耗材分类功能是确保日常运营物资有效管理的关键组成部分&#xff0c;它致力于提高耗材使用的效率和经济性。此功能通过智能化、精细化的管理手段&#xff0c;对校园内各种易耗品进行科学分类与跟踪。 耗材分类功能首先建立在对校园日常运…

Prometheus 监控服务器

Prometheus概述 组件化设置&#xff1a;nginx ,ceph , Prometheus 部署Prometheus服务器 配置时间 安装Prometheus服务器 访问web页面&#xff1a;http://192.168.88.5:9090/ 添加被监控端 监控方式&#xff1a; 拉取&#xff1a;pull。监控端联系被监控端&#xff0c;采集数…

Android sdk 安装已经环境配置

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Android ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 一、下载 二、安装 三、环境配置 我的其他博客 正文 一、下载 1、大家可去官网下载 因为需要魔法 所以就不展示了 2、去下面这…

MySQL期末答辩—仓库管理系统

仓库管理系统&#xff1a;仓库管理系统是一种基于互联网对实际仓库的管理平台&#xff0c;旨在提供一个方便、快捷、安全的存取货物和查询商品信息平台。该系统通过在线用户登录查询&#xff0c;可以线上操作线下具体出/入库操作、查询仓库商品信息、提高仓库运作效率&#xff…

SPI 通信 协议

介绍&#xff1a; SPI&#xff08;Serial Peripheral Interface&#xff09;是由摩托罗拉公司开发的一种通用数据总线 四根通信线&#xff1a; SCK&#xff08;Serial Clock&#xff09; 时钟线MOSI&#xff08;Master Output Slave Input&…

粗糙度轮廓仪功能简介:一体型轮廓仪功能亮点

在精密制造和质量控制领域&#xff0c;表面粗糙度和轮廓的精确测量是至关重要的。一体型轮廓仪为这一需求提供了全面的解决方案。它采用超高精度纳米衍射光学测量系统、超高直线度研磨级摩擦导轨、高性能直流伺服驱动系统、高性能计算机控制系统技术&#xff0c;为用户提供了一…

90后懒人带娃的福音,小学生古诗词的神器——ChatMoney全能知识库AI软件

本文由 ChatMoney团队出品 因为工作需要&#xff0c;浅尝辄止般的用了一下ChatMoney全能知识库AI软件。一些长期积存的问题迎刃而解&#xff0c;但这只是一款企业级大模型功能的冰山一角。让自己尤为惊奇的是里面涵盖的各类资料系统&#xff0c;居然能助力宝妈宝爸带娃脱困。只…

AIGC对设计行业的影响与启发:AIGC设计能替代真正的设计师吗?

随着科技的飞速发展&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;技术在设计行业的应用日益广泛&#xff0c;引发了广泛的讨论和关注。AIGC以其高效、多样化的生成能力&#xff0c;为设计行业带来了前所未有的变革。然而&#xff0c;关于AIGC是否能替代真正的设计…

Rustdesk如何编译代码实现安装后不会显示主界面,不会在右下角出现托盘图标,作为后台服务运行

环境&#xff1a; Rustdesk1.1.9 问题描述&#xff1a; Rustdesk如何编译代码实现安装后不会显示主界面&#xff0c;不会在右下角出现托盘图标&#xff0c;作为后台服务运行 解决方案&#xff1a; 可以自定义进程名称和图标&#xff0c;不会显示主界面&#xff0c;不会在…

uboo对内存操作读写命令的基本使用

内存操作命令 直接对DRAM进行读写的操作,uboot常用的内存操作命令有md,nm,mm,mw,cp和cmp. md命令: 显示内存值 # md md - memory display Usage: md [.b, .w, .l, .q] address [# of objects] b:1个字节 byte w:2个字节 world l:4个字节 long of objects 以word 为单位的1…

搭建个人博客及错误记录

搭建个人博客及错误记录 文章目录 搭建个人博客及错误记录需要用到的网址2.推荐两个参考教学视频3.发布一篇博客个人主题配置的提醒localhost拒绝连接问题解决办法ssh -T gitgithub.com失败问题解决Deployer not found:git解决 可以根据目录解决遇到的相同问题 需要用到的网址 …

昇思25天学习打卡营第3天|yulang

今天主要学习03-张量Tensor&#xff0c;主要包含了处理创建张量、张量的属性、张量索引和张量运算&#xff0c;稀疏张量&#xff0c;有点看不太懂&#xff0c;感觉要开始入门到放弃了&#xff1f;张量在构建和训练深度学习模型中的实际应用&#xff0c;如卷积神经网络。 张量&a…

Vue入门-如何创建一个Vue实例

创建一个一个Vue实例总共分为四步&#xff1a; 1.创建一个容器 2.引包&#xff1a;地址栏搜索v2.cn.vuejs.org这是vue2的官网地址&#xff0c;把2去掉就是vue3的官网地址&#xff0c;我们的包分为开发版本和生产版本&#xff0c;开发版本包含完整的警告和调试模式生产版本删除…

【Python机器学习】算法链与管道——用预处理进行参数选择的注意项

对于许多机器学习算法&#xff0c;提供的特定数据表示非常重要。比如&#xff0c;首先对数据进行缩放&#xff0c;然后手动合并特征&#xff0c;再利用无监督机器学习来学习特征。因此&#xff0c;大多数机器学习应用不仅需要应用多个算法&#xff0c;而且还需要将许多不同的处…

BIOS中的设置虽然不少,但其实大部分时候只需进行一些简单的调整

序言 浏览BIOS可能会让人感到不知所措,因为要考虑的设置太多了。但是,你应该在BIOS中进行一些简单的调整,以提高系统的性能和稳定性。我们将向你展示其中的一些调整,并解释你可能想要使用它们的时间和原因。 用密码保护你的BIOS 虽然我们很小心地对用户帐户进行密码保护…

打卡第一天

今天是参加算法训练营的第一天&#xff0c;希望我能把这个训练营坚持下来&#xff0c;希望我的算法编程题的能力有所提升&#xff0c;不再面试挂了&#xff0c;面试总是挂编程题&#xff0c;记录我leetcode刷题数量&#xff1a; 希望我通过这个训练营能够实现两份工作的无缝衔接…