【C++】手把手教你模拟实现 vector

news2025/6/21 4:58:36

目录

一、构造/析构/拷贝

1、构造函数

1️⃣无参的构造函数

2️⃣带参的构造函数

3️⃣类模板的构造函数

2、析构函数

3、拷贝构造

二、修改操作

1、reserve

【错误版本】

🌟【解答】正确版本

2、resize

3、push_back

 4、pop_back 

5、insert

6、erase

三、遍历

1、operator [ ]

2、迭代器

 3、范围for

四、完整代码


一、构造/析构/拷贝

迭代器是一种新型类型,需要自己定义。成员变量是三个迭代器类型的数据,_start 指向数据块的开始,_finish 指向有效数据的最后,_endOfStorage 指向储存容量的最后。 

1、构造函数

1️⃣无参的构造函数

将成员变量初始化为空即可。

 2️⃣带参的构造函数

提前开辟空间,插入初始化定值。

因为不知道初始化值的类型,所以用匿名对象T()。

初始化之后才能使用 push_back 。

3️⃣类模板的构造函数

 但此时会产生非法的间接寻址

🌟【解答】

解决方法就是 提供一个 int 版本的重载类型:

2、析构函数

首先判断一下_statr是否为空指针,如果为空指针那就不用释放了;不为空指针说明还有数据,需要释放,将指针都置空。

 3、拷贝构造

【类和对象之拷贝构造】:对C++拷贝构造做了一个详细的介绍。
vector 必须调用深拷贝,利用memcpy函数将数据按照字节的方式将 _start 指向的数据一个一个拷贝到 this 指向的空间里,此时就可以成功拷贝。但是当 vector<string> 类型的数据扩容时,会有隐藏的深拷贝。因为此时拷贝的数据是 vector<int> 类型的,即内置类型,内置类型使用memcpy按照字节的方式拷贝是没有问题的。但 string 类是自定义类型的,使用 memcpy 就不能拷贝成功了。所以在拷贝自定义类型的数据时,我们不能使用 memcpy ,可以使用赋值,string 类的赋值就是在异地开辟一块空间,再将数据拷贝过去。vector 的深拷贝可以参考【浅谈 vector 深拷贝】

二、修改操作

1、reserve

功能是预留空间,主要用来扩容。

【步骤】

当数据实际大小大于容量时,就要开始扩容;

在异地开辟一块大小为n的空间;

使用memcpy将原数据拷贝过来,释放原空间;

更新_finish和_endOfStorage。

【错误版本】

会发现程序实际运行起来会崩溃,这是为什么呢? 

🌟【解答】正确版本

size()函数返回 _finish - _start 的值。但是上一步我们已经将 tmp 赋值给了 _start ,所以

size()函数不能成功运行,因此我们可以提前保存 size()数值。

2、resize

功能是开辟空间并初始化。 一般来说,初始化的值是缺省值,不给定的时就使用缺省值初始化(通常默认是用0),给定值时就用这个值初始化。但是这里不可以用0初始化,因为不一定为vector<int>类型,还可能是其他类型。所以这里给的是T()。

T() 本质是一个匿名对象,会自动调用默认构造。对于自定义类型,就会调用默认构造;内置类型没有构造函数。但因为有了模板,内置类型升级了,也有了类似构造函数,就比如int i=int()。这里默认int()是0,而int j=int(1),这里给j初始化的就是1了。

resize()可以分成三种情况:

① n<size()时,直接删除数据。

②第二种size()<n<capacity(),这种情况也不需要扩容,直接将多余的初始化即可,

③n>capacity,扩容并初始化

3、push_back

功能:尾插一个数据

步骤:先判断是否有足够空间,如果没有要扩容;

将需要插入的数据放入_finish;

_finish增加;

 4、pop_back 

功能:尾删一个数据

步骤:finish 向前移一位,注意finish边界问题。

使用empty()函数

5、insert

 功能:插入一个元素

步骤:

相比较 string 类的 insert,这里vector就不太一样了,因为string中的insert使用的还是下标,而这里使用的是迭代器。在pos位置插入val;

首先需要判断 pos 的位置是否合法;

判断是否需要扩容,如果需要扩容要更新pos的位置,这里涉及到迭代器失效的问题。;

将 pos 后的数据向后移一位,在 pos处插入 val;

6、erase

功能:删除一定的数据。但是会涉及到迭代器失效的问题。解决方法就是利用返回值,将 erase 删除位置的下一个元素的位置返回回去,这样it就会更新成被删除元素的的下一个位置。具体细节可以参考【vector 迭代器失效】

首先判断pos位置是否合法
③挪动数据,将pos位置覆盖。从前往后挪动。
④挪动完后,将finish往前挪动。
⑤返回pos,这里返回的是被删除数据的下一个元素的位置。

三、遍历

1、operator [ ]

2、迭代器

 3、范围for

【运行代码】

【vector 常用接口】

四、完整代码

namespace zhou
{
	//这里的vector实现需要使用模板,因为vector就是用模板实例化出各种类型的vector;
	template<class T>
	class vector
	{
	public:
		//迭代器是一种新型类型,需要自己定义;
		typedef T* iterator;
		typedef const T* const_iterator;

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


		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

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

		//vector<int> v(10, 5); 如果调用  n是无符号,T 是 int,会发生类型转换
		// 编译器就会自动调用模板构造函数,int类型不能解引用,就报错了
		//类模板的成员函数使用模板,就可以不限制类型
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		vector(const vector<T>& v)
		{
			_start = new T[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();
			_endOfStorage = _start + v.capacity();
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

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

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//new 不需要检查是否为空,因为如果为空,直接抛异常
				T* tmp = new T[n];
				size_t sz = size();
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_endOfStorage = _start + n;
			}
		}

		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _endOfStorage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				// 扩容后更新pos,解决pos失效的问题
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator start = pos + 1;
			while (start != _finish)
			{
				*(start - 1) = *start;
				++start;
			}

			--_finish;
			return pos;
		}

		void push_back(const T& x)
		{
			if (_finish == _endOfStorage)
			{
				//错误代码
				//reserve(capacity() * 2);
				//正确代码
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			_finish++;
		}

		bool empty()
		{
			return _start == _finish;
		}
		void pop_back()
		{
			assert(!empty());
			_finish--;
		}

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

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

		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

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

		//成员变量三个迭代器
	private:
		iterator _start;		// 指向数据块的开始
		iterator _finish;		// 指向有效数据的尾
		iterator _endOfStorage;  // 指向存储容量的尾
	};

	void func(const vector<int>& v)
	{
		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		/*vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl << endl;*/
	}
	void test_vector1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		func(v);

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;

		for (auto ch : v)
		{
			cout << ch << " ";
		}
	}

	void test_vector2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		//v1.push_back(5);
		func(v1);

		//头插
		v1.insert(v1.begin(), 0);
		func(v1);

		//在pos前插入
		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.insert(pos, 30);
		}
		func(v1);
	}

	void test_vector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		//v1.push_back(5);
		func(v1);

		//在pos前插入
		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.insert(pos, 30);
		}
		func(v1);

		(*pos)++;
		func(v1);
	}

	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		
		func(v1);

		auto pos = find(v1.begin(), v1.end(),3);
		if (pos != v1.end())
		{
			v1.erase(pos);
		}
		cout << (*pos) << endl;
		func(v1);
	}

	void test_vector5()
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		func(v1);

		zhou::vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				it = v1.erase(it);
				//这里erase之后迭代器就失效了,同时偶数也无法正常删除。
			}
			else
			{
				++it;
			}
		}
		func(v1);
	}

	void test_vector6()
	{
		vector<int> v(10u, 5);//在数字后面加上u就是无符号整型
		func(v);
	}

	void test_vector7()
	{
		vector<int> v(10, 5);
		func(v);

		vector<int> v1(v);
		func(v);
	}
	
	void test_vector8()
	{
		vector<std::string> v1(3,"111111111111");
		for (auto ch : v1)
		{
			cout << ch << " ";
		}
		
		vector<std::string> v2(v1);
		for (auto ch : v2)
		{
			cout << ch << " ";
		}
		v2.push_back("22222222222");
		v2.push_back("22222222222");
		v2.push_back("22222222222");
	}
}

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

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

相关文章

【LV15 DAY7 阻塞和非阻塞】

一、五种IO模型------读写外设数据的方式 阻塞: 不能操作就睡觉非阻塞&#xff1a;不能操作就返回错误 多路复用&#xff1a;委托中介监控信号驱动&#xff1a;让内核如果能操作时发信号&#xff0c;在信号处理函数中操作异步IO&#xff1a;向内核注册操作请求&#xff0c;内核…

Web本体语言OWL

语义网&#xff08;Semantic Web&#xff09;&#xff1a; 语义网是万维网联盟&#xff08;W3C&#xff09;提出的一种愿景&#xff0c;旨在增强现有Web的表达能力和智能处理能力&#xff0c;通过标准化的技术手段赋予网络数据更加精确和可计算的语义&#xff0c;使得机器能够…

Ubuntu18/20运行ORB-SLAM3

ORB-SLAM3复现(ubuntu18/20) 文章目录 ORB-SLAM3复现(ubuntu18/20)1 坐标系与外参Intrinsic parameters2 内参Intrinsic parameters2.1 相机内参① 针孔模型Pinhole② KannalaBrandt8模型③ Rectified相机 2.2 IMU内参 3 VI标定—外参3.1 Visual calibration3.2 Inertial calib…

【Appium问题】每次启动appium都会安装一次uiautomator

问题 每次启动appium&#xff0c;都需要安装一次uiautomator2比较麻烦 解决 在配置文件capabilities 中增加参数skipServerInstallationTrue

CSDN,最全API接口测试入门教程到实战

由于自己想学习API方面的测试&#xff0c;但是市面上搜不到相关的图书可以系统学习&#xff0c;网上的内容又零零散散&#xff0c;适合有点API开发基础的人去搜索。为了方面新手学习API测试&#xff0c;现在整理了他人的宝贵经验和自己的学习心得&#xff0c;尽量在一篇文章中囊…

git删除comimit提交的记录

文章目录 本地的删除远程同步修改上次提交更多详情阅读 本地的删除 例如我的提交历史如下 commit 58211e7a5da5e74171e90d8b90b2f00881a48d3a Author: test <test36nu.com> Date: Fri Sep 22 20:55:38 2017 0800add d.txtcommit 0fb295fe0e0276f0c81df61c4fd853b7a00…

【开源】JAVA+Vue.js实现食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

水果音乐编曲软件 FL Studio v21.2.3.4004 中文免费版(附中文设置教程)以及新功能介绍

FL studio21中文别名水果编曲软件&#xff0c;是一款全能的音乐制作软件&#xff0c;包括编曲、录音、剪辑和混音等诸多功能&#xff0c;让你的电脑编程一个全能的录音室&#xff0c;它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得…

Linux编程3.6 进程-其他进程及函数

1、僵尸进程 子进程结束但是没有完全释放内存&#xff08;在内核中的task_struct没有释放&#xff09;&#xff0c;该进程就成为僵尸进程。当僵尸进程的父进程结束后就会被init进程领养&#xff0c;最终被回收。避免僵尸进程 让僵尸进程的父进程来回收&#xff0c;父进程每隔一…

StringBuffer与StringBuilder的常用方法及源码分析。

StringBuffer与StringBuilder的常用方法及源码分析 1. 常用方法2. 源码分析 StringBuffer和StringBuilder都是用于处理字符串的可变对象&#xff0c;它们之间的主要区别在于StringBuffer是线程安全的&#xff0c;而StringBuilder是非线程安全的。 1. 常用方法 append()&#…

使用jar命令删除.jar文件中的重复的类和目录并重新打包

引言&#xff1a; android项目&#xff0c;引入的 .jar包 和 .aar中 有相同的类&#xff0c;导致编译冲突&#xff0c;由于这些依赖项没有上传到Maven仓库&#xff0c;无法使用 exclude 排除&#xff0c;只能尝试修改jar文件&#xff0c;删除重复的代码&#xff0c;再重新打包…

【深度学习笔记】6_8 长短期记忆(LSTM)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.8 长短期记忆&#xff08;LSTM&#xff09; 本节将介绍另一种常用的门控循环神经网络&#xff1a;长短期记忆&#xff08;long shor…

qt自绘标尺,鼠标拖动画线测量两点距离

效果如图&#xff1a; 图像显示窗口元素组成&#xff1a; 图像显示窗口整体构成&#xff1a; 长度测量窗口ui&#xff1a; 思路&#xff1a; 首先自定了一个RulerWidget&#xff0c;其中有一个布局&#xff0c;布局中包含自定义的水平Ruler、自定义垂直Ruler、单位QLab…

洗地机怎么选|洗地机哪款好用?添可、希亦、美的洗地机哪个最耐用质量好?

在现代生活中&#xff0c;屋内清洁是一项必不可少的工作&#xff0c;但也是一项费时费力的工作。随着科技的进步&#xff0c;家庭清洁工具也正经历着革命性的变革。洗地机&#xff0c;一种集吸尘、拖地、清洗于一体的智能家居清洁工具&#xff0c;正逐渐成为现代家庭必备的家电…

15. 三数之和 - 力扣

1. 题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 …

华容道问题求解_详细设计(四)之查找算法2_BFS

&#xff08;续上篇&#xff09; 利用BFS查找&#xff0c;会找到最短路径&#xff08;没有权重的图&#xff09;&#xff0c;这个道理比较简单&#xff0c;这是由于寻找路径的方法都是从起点或者接近起点的位置开始的。查找过程如果画出图来&#xff0c;类似于一圈圈的放大&…

C++初阶:类与对象(中篇)

目录 2. 类的六个默认成员函数2.1 构造函数2.1.1 构造函数的定义方式 2.2 析构函数2.2.1 析构函数定义方式 2.3拷贝构造函数2.3.1 拷贝构造函数的定义方式2.3.2 深拷贝与浅拷贝 2.4 赋值运算符的重载2.4.1 运算符重载2.4.2 运算符的重载的定义方式2.4.3 默认成员函数&#xff1…

Redis 常见数据类型(对象类型)和应用案列

前言: 每次你在游戏中看到玩家排行榜&#xff0c;或者在音乐应用中浏览热门歌单&#xff0c;有没有想过这个排行榜是如何做到实时更新的&#xff1f;当然&#xff0c;依靠 Redis 即可做到。 在技术领域&#xff0c;我们经常听到「键值存储」 这个词。但在 Redis 的世界里&…

VBA技术资料MF128:批量创建文件夹及子文件夹

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

低密度奇偶校验码LDPC(十)——LDPC码的密度进化

一、密度进化的概念 二、规则LDPC码的密度进化算法(SPA算法) 算法变量表 VN更新的密度进化 CN更新的密度进化 算法总结 程序仿真 参考文献 [1] 白宝明 孙韶辉 王加庆. 5G 移动通信中的信道编码[M]. 北京: 电子工业出版社, 2018. [2] William E. Ryan, Shu Lin. Channel Co…