【C++】手撕 Vector类

目录

1,vector类框架

2,vector ()

3,pinrt()

4,vector(int n, const T& value = T())

5,vector(const vector& v)

6,vector(InputIterator first, InputIterator last)

7,~vector()

8,iterator begin()

9,iterator end()

10,size() const

11,capacity() const

12,reserve(size_t n)

13,resize(size_t n, const T& value = T())

14,push_back(const T& x)

15,pop_back()

16,insert(iterator pos, const T& x)

17,erase(iterator pos)

18,empty()

19,operator[](size_t pos)

20,operator= (vector v)

21,总结


上面我们认识了 vector 类,有了一个大概的理解,下面我们来实现一下 vector 类的框架,来更好的熟悉 vector 类,也让我们对其有着更深的理解; 

1,vector类框架

我们先写一个 vector 类的基本框架;

namespace newVector
{
	template<class T>
	class vector
	{
	public:

		// Vector的迭代器是一个原生指针
		typedef T* iterator;

	private:

		iterator _start = nullptr; // 指向数据块的开始

		iterator _finish = nullptr; // 指向有效数据的尾

		iterator _endOfStorage = nullptr; // 指向存储容量的尾

	};
}

vector 类里面是可以包含很多类型的,所以我们用模板来表示,以应用各种场景,vector 类里面多用迭代器的方式来表示,vector 的迭代器其实就是一个原生指针;

_start 指向数据块的开始,_finish 指向有效数据的尾,_endOfStorage 指向存储容量的尾;

2,vector ()

vector()
{}

因为我们在构造框架的时候已经给了缺省值,所以可以不用在写了;

可以看到这里已经初始化了;

3,pinrt()

就是打印输出嘛,方便后续测试;

		void pinrt()
		{
			for (size_t i = 0; i < size(); i++)
			{
				cout << _start[i] << " ";
			}
		}

4,vector(int n, const T& value = T())

我们都会用,初始化 n 个 value;

		vector(int n, const T& value = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(value);
			}
		}

有人不知道缺省值给个 T()是什么意思,首先 T()是匿名对象,默认就是编译器对内置类型的初始化; 

测试一下:

int main()
{
	newVector::vector<int> v1(5, 8);
	v1.pinrt();
	return 0;
}

现在我们不给初始化的值试试:

int main()
{
	newVector::vector<int> v1(5);
	v1.pinrt();
	return 0;
}

 默认初始化为0;

5,vector(const vector<T>& v)

拷贝构造(深拷贝)

		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			//memcpy(_start, v._start, sizeof(T)*v.size());
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
		}

为什么不用 memcpy 呢,上面那个明显要便捷一点,因为 memcpy 是浅拷贝,如果遇到 T是string类的时候就会因为析构函数多次析构同一块空间而报错,而下面这个挨个赋值是深拷贝,他们两个 _start 不会指向同一块空间;

int main()
{
	newVector::vector<int> v1(5,8);
	newVector::vector<int> v2(v1);
	v2.pinrt();
	return 0;
}

可以看到也是 OK 的;

6,vector(InputIterator first, InputIterator last)

这个要配合模板使用,这代表一个范围,只要是同类型的都可以;

		template<class InputIterator>

		vector(InputIterator first, InputIterator last)
		{
			reserve(last - first + 1);
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

先扩容嘛,像这种区间都是左闭右开的,然后再挨个尾插即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	v1.pinrt();
	return 0;
}

只要是同类型的都可以,像这种的数组都行;

7,~vector()

析构函数

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

delete 后面一定要带 [ ] ,因为析构的是一段连续的空间,就看做是数组即可,然后再将各个迭代器置空即可;

int main()
{
	newVector::vector<int> v1(5,8);
	v1.~vector();
	v1.pinrt();
	return 0;
}

8,iterator begin()

指向第一个元素的迭代器

		iterator begin()
		{
			return _start;
		}

直接返回 _start 即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *v1.begin();
	return 0;
}

9,iterator end()

指向最后一个元素下一个元素的迭代器

		iterator end()
		{
			return _finish;
		}

直接返回 _finish 即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *v1.end();
	return 0;
}

直接随机数了;

换个思路,试一下:

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << *(v1.end()-1);
	return 0;
}

我们找他前一个迭代器,果然是最后一个数;

10,size() const

返回有效数据个数

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

直接迭代器相减即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.size();
	return 0;
}

11,capacity() const

返回容量大小

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

直接迭代器相减即可,差值就是容量大小;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.capacity();
	return 0;
}

12,reserve(size_t n)

扩容

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t old = size();
				if (_start)
				{
					//memcpy(tmp, _start, old * sizeof(T));
					for (size_t i = 0; i < old; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + old;
				_endOfStorage = _start + n;
			}
		}

当要扩容时才用的着,先判断,然后开辟一段需要的空间,之后就是拷贝赋值了,这里我们还是没有用 memcpy 因为是浅拷贝,然后再释放原空间,再将迭代器重新赋值即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.capacity() << endl;

	v1.reserve(20);
	cout << v1.capacity() << endl;
	return 0;
}

一目了然;

13,resize(size_t n, const T& value = T())

更改有效数据个数,不够则填充,可以指定填充数据;

		void resize(size_t n, const T& value = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				while (_finish!=_start+n)
				{
					push_back(value);
				}
			}
		}

当缩减数据时直接把 _finish 往前移即可,当扩容时先扩容,然后进行填充;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr,arr+5);
	cout << v1.size() << endl;

	v1.resize(10);
	cout << v1.size() << endl;

	v1.resize(15, 6);
	cout << v1.size() << endl;
	v1.pinrt();
	return 0;
}

可以看到,当我们不指定填充时默认填充0;

14,push_back(const T& x)

尾插

		void push_back(const T& x)
		{
			if (_finish == _endOfStorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}

首先要检查是否需要扩容,然后赋值,将 _finish 往后移一位即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	v1.push_back(6);
	v1.push_back(7);
	v1.pinrt();
	return 0;
}

 插入十分成功;

15,pop_back()

尾删

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

像这种数组类型的,直接对其迭代器动手就行,直接 _finish 往前移一位即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	v1.pop_back();
	v1.pop_back();
	v1.pinrt();
	return 0;
}

删除非常顺利;

16,insert(iterator pos, const T& x)

指定位置插入

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			size_t old = pos - _start;
			if (_finish == _endOfStorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			pos = _start + old;
			memcpy(pos + 1, pos, (_finish - pos) * sizeof(T));
			*pos = x;
			_finish++;
			return pos;
		}

这里会面临一个迭代器失效的问题,当扩容后,原本的 pos 还是指向旧空间就失效了,所以我们要更新 pos ;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	auto pos = find(v1.begin(), v1.end(), 1);
	v1.insert(pos, 9);

	pos= find(v1.begin(), v1.end(), 5);
	v1.insert(pos, 9);
	v1.pinrt();
	return 0;
}

完美插入;

17,erase(iterator pos)

擦除指定位置

		iterator erase(iterator pos)
		{
			assert(pos >= 0 && pos <= _finish);
			memcpy(pos, pos + 1, sizeof(T)*(_finish - pos));
			_finish--;
			return pos;
		}

先断言判断,然后直接往前移一位覆盖即可,再更新一下 _finish ;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);

	auto pos = find(v1.begin(), v1.end(), 1);
	v1.erase(pos);

	pos = find(v1.begin(), v1.end(), 5);
	v1.erase(pos);
	v1.pinrt();
	return 0;
}

也是成功擦除了;

18,empty()

判空

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

当为空时返回真,反之亦然;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	cout << v1.empty() << endl;

	newVector::vector<int> v2;
	cout << v2.empty();
	return 0;
}

19,operator[](size_t pos)

返回下标对应的值;

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

直接像数组一样取值返回即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	cout << v1[0] << " " << v1[4];
	return 0;
}

写法也是跟数组一样简单;

20,operator= (vector<T> v)

赋值,深拷贝

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

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

直接一手交换即可;

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	newVector::vector<int> v1(arr, arr + 5);
	
	newVector::vector<int> v2 = v1;
	v2.pinrt();
	return 0;
}

 

21,总结

我们就先搞一个大概的,其中还有很多分支,比如我们写的是擦除某个数据,其实也可以擦除某个范围,这些就靠大家去摸索,查阅文档了;

vector类的实现就到这里了;

加油!

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

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

相关文章

20231228在Firefly的AIO-3399J开发板的Android11的Firefly的AIO-3399J开发板的DTS配置单前置摄像头ov13850

20231228在Firefly的AIO-3399J开发板的Android11的Firefly的AIO-3399J开发板的DTS配置单前置摄像头ov13850 2023/12/28 12:30 开发板&#xff1a;Firefly的AIO-3399J【RK3399】 SDK&#xff1a;rk3399-android-11-r20211216.tar.xz【Android11】 Android11.0.tar.bz2.aa【ToyBr…

uni-app 前后端调用实例 基于Springboot

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

怎么解决 Nginx反向代理加载速度慢?

Nginx反向代理加载速度慢可能由多种原因引起&#xff0c;以下是一些可能的解决方法&#xff1a; 1&#xff0c;网络延迟&#xff1a; 检查目标服务器的网络状况&#xff0c;确保其网络连接正常。如果目标服务器位于不同的地理位置&#xff0c;可能会有较大的网络延迟。考虑使用…

win10系统请将eNSP相关应用程序添加到windows firewall的允许程序列表,并允许其在公用网络上运行!的解决办法

很多学习网络的小伙伴&#xff0c;在下载安装eNSP后&#xff0c;打开程序跳出&#xff1a;请将eNSP相关应用程序添加到windows firewall的允许程序列表&#xff0c;并允许其在公用网络上运行! 是不是挺闹心的! 其实&#xff0c;原因是很简单&#xff0c;就是win10系统防火墙访…

本地git服务器的使用

Windows上使用&#xff1a; 首先要在windows开发机上生成密钥&#xff1a; 1.安装git&#xff0c;首先去git官网下载git&#xff0c;https://git-scm.com/downloads&#xff0c;下载.exe格式并安装。 2.从程序目录启动“Git Bash” 3.键入命令&#xff1a;ssh-keygen -t rsa -…

快速上手:探索Spring MVC的学习秘籍!

SpringMVC概述 1&#xff0c;SpringMVC入门案例1.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤9:浏览器访问步骤10:修改Controller返回值解…

YOLOv8主干改进 更换柱状神经网络RevCol

一、Reversible Column Networks论文 论文地址:2212.11696.pdf (arxiv.org) 二、Reversible Column Networks结构 Reversible Column Networks 是一种用于量子计算的新型结构。它由一系列可逆操作组成,可以在量子计算中进行高效的信息传递和处理,具有可扩展性、灵活性、…

HTML教程(2)——基础标签

一、HTML的元数据 <meta>标签定义关于 HTML 文档的元数据。元数据是关于数据的数据&#xff08;信息&#xff09;,其始终位于<html>元素内&#xff0c;通常用于指定字符集、页面描述、关键词、文档作者和视口设置&#xff1b; 元数据不会显示在页面上&#xff0c…

回顾2023在CSDN的足迹与2024展望

目录 一、关于博主 二、2023的历程 1、博客分类 2、年度创作数据 3、解锁勋章 4、主要的方向 二、技术感悟 1、技术深入 2、还是实践 三、展望2024 今天是2024年的第一天&#xff0c;告别2023年&#xff0c;让我们以全新的姿态&#xff0c;去迎接新的一年的挑战。2023年…

尝试读取挪威 3d radar数据

1 原因 挪威的三维探地雷达很有特色&#xff0c;步进频率采样&#xff0c;与传统的GPR很不同&#xff0c;一个天线就拥有N个天线的功能。很想看看他们采集的数据是否清晰。 之前浙大学报审稿审到华南理工用的他们这个雷达。 图像来自知乎&#xff1a;若侵权&#xff0c;请告…

Windows反调试技术学习

Windows反调试 前言元旦快乐&#xff01;&#xff01;&#xff01;通过 API 调用IsDebuggerPresentCheckRemoteDebuggerPresent&#xff08;NtQueryInformationProcess&#xff09;OutputDebugStringZwSetInformationThread&#xff08;ThreadHideFromDebugger&#xff09; 手动…

【华为机试】2023年真题B卷(python)-喊七的次数重排

一、题目 题目描述&#xff1a; 喊7是一个传统的聚会游戏&#xff0c;N个人围成一圈&#xff0c;按顺时针从1到N编号。 编号为1的人从1开始喊数&#xff0c;下一个人喊的数字为上一个人的数字加1&#xff0c;但是当将要喊出来的数字是7的倍数或者数字本身含有7的话&#xff0c;…

windows无命令升级降级node版本

1. node最新版本下载链接 点击最新下载链接&#xff0c;找到对应版本下载并解压 2. 通过命令where node找到node.exe位置 3. 将该位置的node.exe替换为下载解压的最新node.exe 4. 重新执行node -v查看版本 --------------------------------------------------------------…

ETLCloud X 明道云实现无缝数据连接

明道云作为一款云端协作工具&#xff0c;为企业提供高效的沟通、协作和数据分析服务。它可以实现企业内部沟通和协作的高效性和一体化&#xff0c;并提供数据分析功能&#xff0c;让企业能够更好地理解业务和决策。 一、传统方式同步数据的痛点 传统方式同步数据需要手动进行…

C语言学习----存储类别

存储类别 &#x1f33f;本文是C Primer Pluse 中文版第12章的部分内容整理 &#x1f331;主要是围绕C中作用域 链接 存储期 展开 &#xff0c;是后面进行多文件管理的基础~ &#x1f308;概要 &#x1f34e;明确对象 变量名 标识符的基本概念和含义 &#x1f350;作用域和链接描…

【LLM+RS】LLM在推荐系统的实践应用(华为诺亚)

note LLM用于推荐主要还是解决推荐系统加入open domain 的知识。可以基于具体推荐场景数据做SFT。学习华为诺亚-技术分享-LLM在推荐系统的实践应用。 文章目录 note一、背景和问题二、推荐系统中哪里使用LLM1. 特征工程2. 特征编码3. 打分排序 三、推荐系统中如何使用LLM四、挑…

Nexus私服简介及搭建(Linux3.62版本)

文章目录 一、Nexus的安装1、运行程序2、查看运行日志和初始密码3、启动配置文件的修改 二、Nexus的使用1、Nexus使用流程说明2、库类型说明2.1、maven-public库配置说明2.2、maven-central库配置说明 3、用户本地配置使用maven-public库3.1、禁用了匿名访问&#xff0c;额外需…

【JavaScript】复制文本到剪切板

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

鸿蒙 DevEco Studio 3.1 入门指南

本文主要记录开发者入门&#xff0c;从软件安装到项目运行&#xff0c;以及后续的学习 1&#xff0c;配置开发环境 1.1 下载安装包 官网下载链接 点击立即下载找到对应版版本 下载完成&#xff0c;按照提示默认安装即可 1.2 下载SDK及工具链 运行已安装的DevEco Studio&…

【Redis-02】Redis数据结构与对象原理 -上篇

Redis本质上是一个数据结构服务器&#xff0c;使用C语言编写&#xff0c;是基于内存的一种数据结构存储系统&#xff0c;它可以用作数据库、缓存或者消息中间件。 我们经常使用的redis的数据结构有5种&#xff0c;分别是&#xff1a;string(字符串)、list(列表)、hash(哈希)、s…