vector模拟实现【C++】

文章目录

  • 全部的实现代码放在了文章末尾
  • 准备工作
    • 包含头文件
    • 定义命名空间和类
      • 类的成员变量
  • 迭代器
    • 迭代器获取函数
  • 构造函数
    • 默认构造
    • 使用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.mfbz.cn/a/768706.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上位机网络通讯

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using System; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace 上位机网络通讯 {public partial class Form1 : Form{public Form1(){Initializ…

BUG TypeError: GPT2Model.forward() got an unexpected keyword argument ‘past’

TypeError: GPT2Model.forward() got an unexpected keyword argument past’ 环境 transformers 4.38.1详情 这是由于新版的transformers 对GPT2Model.forward() 参数进行了改变导致的错误。具体是past名称改为了 past_key_values 。 解决方法 找到错误语…

黑马点评-Redis的缓存击穿,缓存雪崩,缓存穿透,互斥锁,逻辑过期

文章目录 1.缓存穿透2.缓存雪崩3.缓存击穿3.1 互斥锁3.2 基于逻辑过期 1.缓存穿透 解决办法 写入NULL值到Redis缓存&#xff0c;以后就会命中Redis的控制缓存而不会出现请求直接打到数据库的问题&#xff01; 代码 2.缓存雪崩 这个概念很好理解&#xff0c;雪崩就是无数的…

开发者聊科学作息时间表

非常有幸对科学作息时间表app的开发者做一次采访。 问&#xff1a;你对科学作息时间表app满意么&#xff1f; 答&#xff1a;非常不满意&#xff0c;我们的设想是让他更智能&#xff0c;更多的提醒方式&#xff0c;更好的交互体验。如果作为一个闹钟他是非常不合格的&#xff0…

视图库对接系列(GA-T 1400)三、代码生成

项目准备 我们把需要的第三方包和需要生成的库引入到对应的**基础包**中对应版本号 <properties><java.version>1.8</java.version><httpclient.version>4.5</httpclient.version><httpcore.version>4.4.12</httpcore.version><…

tinyshop项目部署

参考软件测试之测试用例设计&#xff08;四&#xff09;_管理后台 测试用例-CSDN博客 1、下载xampp 2、修改apache和mysql的端口分别为4431 &#xff0c;8013和3306 3、访问页面&#xff1a;输入ip:端口号&#xff0c;出现以下页面即成功 4、安装tinyshop商城 将解压的tinys…

Chart.js四个示例

示例代码在图片后面&#xff0c;点赞加关注&#xff0c;谢谢 条形图 雷达图 折线图 圆环图 完整例子代码 具体代码在干什么看粗体加重的注释 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <me…

OceanBase Meetup北京站|跨行业应用场景中的一体化分布式数据库:AI赋能下的探索与实践

随着业务规模的不断扩张和数据处理需求的日益复杂化&#xff0c;传统数据库架构逐渐暴露出业务稳定性波动、扩展性受限、处理效率降低以及运营成本高等一系列问题。众多行业及其业务场景纷纷踏上了数据库现代化升级之路。 为应对这些挑战&#xff0c;7月6日&#xff0c;OceanB…

搭建个人博客及错误记录

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

德国威步的技术演进之路(下):从云端许可管理到硬件加密狗的创新

从单机用户许可证到WkNET网络浮点授权的推出&#xff0c;再到引入使用次数和丰富的时间许可证管理&#xff0c;德国威步产品不断满足市场对灵活性和可扩展性的需求。TCP/IP浮动网络许可证进一步展示了威步技术在网络时代的创新应用。借助于2009年推出的借用许可证以及2015年推出…

如何选择适合自己的笔记本电脑

在现代社会中&#xff0c;笔记本电脑已经成为人们工作、学习和娱乐的重要工具。然而&#xff0c;面对市场上琳琅满目的笔记本电脑产品&#xff0c;如何选择一款适合自己的笔记本电脑呢&#xff1f;本文将为您提供一些有用的建议。 首先&#xff0c;确定您的使用需求。不同的用户…

新手教学系列——慎用Flask-SQLAlchemy慢日志记录

在使用 Flask-SQLAlchemy 开发应用时,了解和避免潜在的问题是非常重要的。特别是在常驻进程和循环执行任务的场景下,慢查询记录功能(SQLALCHEMYRECORDQUERIES)可能会引发严重的内存泄漏问题。本文将详细介绍这个问题,并提供解决方案,帮助你在开发过程中避免掉入这些陷阱。…

为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}(保姆级包括安装QT)超详细记录版

为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}【保姆级包括安装QT】超详细记录版 Chapter1 为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}(保姆级包括安装QT)超详细记录版一. 安装QT程…

深入了解自动化:聊聊什么项目适合做自动化测试?

自动化测试 什么是自动化测 什么是自动化测试&#xff1f; 随着软件产业的不断发展&#xff0c;市场对软件周期的要求越来越高&#xff0c;于是催生了各种开发模式&#xff0c;如大家熟知的敏捷开发&#xff0c;从而对测试提出了更高的要求。此时&#xff0c;产生了自动化测试…

2024年港澳台联考考生成绩数据分析来啦

分数线 出炉 2024年的港澳台联考正式出分&#xff01;根据考生成绩&#xff0c;全国联招划档线如下&#xff1a; 一、本科批次 &#xff08;一&#xff09;普通类院校&#xff08;专业&#xff09;&#xff1a;文史类365分、理工类390分&#xff08;部分院校执行高分线&#…

算法基础入门 - 2.栈、队列、链表

文章目录 算法基础入门第二章 栈、队列、链表2.1 队列2.2 栈2.3 纸牌游戏2.4 链表如何建立链表?1.我们需要一个头指针(head)指向链表的初始。链表还没建立时头指针head为空2.建立第一个结点3.设置刚创建的这个结点的数据域(左半)和指针域(右半)4.设置头指针,头指针可方便…

10 - matlab m_map地学绘图工具基础函数 - 绘制多边形区域、流线图、散点图和添加注释的有关函数

10 - matlab m_map地学绘图工具基础函数 - 绘制多边形区域、流线图、散点图和添加注释的有关函数 0. 引言1. 关于m_patch2. 关于m_streamline3. 关于m_scatter4. 关于m_annotation5. 结语 0. 引言 本篇介绍下m_map中绘制多边形区域函数&#xff08;m_patch&#xff09;、绘制流…

数据库组成及原理

属性&#xff1a; 把数据库中的一个表类比成一个公司&#xff0c;那么公司里的每个人都是一个“属性”&#xff08;表中的一个字段视为一个属性&#xff09;&#xff0c;不管老板还是员工&#xff0c;只要是公司里的人&#xff0c;就都是一个属性。 主键&#xff1a; 老板就是“…

Docker安装PostgreSQL详细教程

本章教程,使用Docker安装PostgreSQL具体步骤。 一、拉取镜像 docker pull postgres二、启动容器 docker run -it --name postgres --restart always -e POSTGRES_PASSWORD=123456 -e

配置windows环境下独立浏览器爬虫方案【不依赖系统环境与chrome】

引言 由于部署浏览器爬虫的机器浏览器版本不同&#xff0c;同时也不想因为部署了爬虫导致影响系统浏览器数据&#xff0c;以及避免爬虫过程中遇到的chrome与webdriver版本冲突。我决定将特定版本的chrome浏览器与webdriver下载到项目目录内&#xff0c;同时chrome_driver在初始…