C++-vector模拟实现

news2024/11/27 4:28:14

###vector底层相当于是数组,查看源码可以发现,这个类的私有成员变量是三个迭代器;在实现时迭代器就可以当作是vector里面的元素的指针类型;

###vector是一个类模板,实现时也应当按照这样的写法用一个模板去实现,类模板vector中数据类型是T;

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;

_start指向vector的开始地址,_finish指向vector的有效元素的结束地址,_end_of_storage指向vector最大存储数据的地址; 

一、迭代器

typedef T* iterator;
typedef const T* const_iterator;

//迭代器
iterator begin()
{
	return _start;
}
const_iterator begin()const
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator end()const
{
	return _finish;
}

二、容量

		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
		bool empty()const
		{
			return size() == 0;
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				//memcpy(tmp, _start, size() * sizeof(T));//浅拷贝
				for (size_t i = 0; i < old_size; i++)//深拷贝
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				_finish = tmp + old_size;
				_end_of_storage = tmp + n;
			}
		}
		void resize(size_t n, const T& val = T())//匿名对象作为缺省值
		{
			if (n < capacity())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				for (iterator i = _finish ; i < _start + n; i++)
				{
					Insert(i,val);
				}
			}
		}

1、reserve

基本思路:开辟一个临时的数组存放T类型的数据,让这个数组的大小为指定的n,将vector对象中的数据全部放到这个临时数组中,再清空vector中的数据,最后让vector对象存放数据的地址就是这个临时数组的地址;

两个关键点:

  1. old_size:若是不先记录下size的话,后面给_finish重新赋值时使用到size(),size()函数中_size不再是原来的_size,而是tmp,此时用_size去减_finish会出错;
  2. 深、浅拷贝问题:若是使用memcpy就是浅拷贝,对于vector中是内置类型的数据可以,但是若是vector对象中涉及到自定义类型,例如string为数据的情况,浅拷贝拷贝的是地址,那么tmp中的数据的地址就是vector对象中的数据地址,之后再delete掉_start,就相当于把要存放数据的tmp中的数据给释放了,这样会让数据丢失;所以一个一个赋值,对于内置类型无影响,对于自定义类型,例如string的类型,就是赋值重载,string实现时是深拷贝,这样就解决了。

2、resize

基本思路:若是比原来的size小,那么就让_finish等于_start+n,这样就访问不到n之后的数据了;若是比原来的size大,没有给值就用匿名对象T(),对于内置类型给初始值(0、空指针、0.0一类),对于自定义类型,调用默认构造函数,实现部分:先扩容,再插入数据。

三、元素获取

		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 push_back(const T& val)
		{
			if (_end_of_storage == _finish)
			{
				reserve(size() == 0 ? 4 : 2 * capacity());
			}
			*_finish = val;
			_finish++;
		}
		void pop_back()
		{
			assert(size() > 0);
			_finish--;
		}
		iterator Insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_end_of_storage == _finish)
			{
				size_t len = pos - _start;
				reserve(size() == 0 ? 4 : 2 * capacity());
				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 && pos < _finish);
			iterator tmp = pos + 1;
			while (tmp < _finish)
			{
				*(tmp - 1) = *tmp;
				tmp++;
			}
			--_finish;
			return pos;
		}

1、insert和erase调用时迭代器失效的问题

insert:实现时若是涉及到空间不够要扩容,扩容时要先记录下pos和_start之间的距离,扩容之后_start不是之前的了,那么pos也要跟着更新;此时在调用insert时若是插入之后还要访问pos就要更新pos(因为pos已经改变了,若是不更新就使用原来的pos会找不到原来的数据)

就算没有扩容,空间够,insert之后使用到pos还是要先更新,因为此时的pos指向的是新插入进来的数据,而不是我们想要访问的原来的数据。

erase:erase的迭代器失效举一个例子:

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	container_print(v);
	cout << "size:" << v.size() << endl;
	cout << "capacity:" << v.capacity() << endl;
	vector<int>::iterator p = v.begin();
	while(p < v.end())
	{
		if (*p % 2 == 0)
		{
			v.Erase(p);
		}
		p++;
	}
	container_print(v);

运行结果似乎很正确,那再看:

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);
	v.push_back(5);

	container_print(v);
	cout << "size:" << v.size() << endl;
	cout << "capacity:" << v.capacity() << endl;
	vector<int>::iterator p = v.begin();
	while(p < v.end())
	{
		if (*p % 2 == 0)
		{
			v.Erase(p);
		}
		p++;
	}
	container_print(v);

此时会发现有偶数没有删除完全,这是因为迭代器失效了

原因:erase的实现中删除了pos指向的数据之后返回pos,此时的pos指向的是原来要删除的数据的下一个数据,在这个删除偶数的例子中,{1,2,3,4,5}pos指向2.删除之后pos指向3,再加加就是指向4,刚好到了下一个偶数,删除之后,pos指向5,再加加,循环结束,这里只是一个巧合;数据是{1,2,3,4,4,5}删除完2,pos指向第一个4,删除之后,pos指向第二个4,再加加那么pos指向是5了,就跳过了这个4,此时就是迭代器失效了;

为了解决,erase要使用到pos就要更新迭代器;

	while(p < v.end())
	{
		if (*p % 2 == 0)
		{
			p=v.Erase(p);
		}
		else
		{
			p++;
		}
	}

这样写,每次删除完之后,pos指向就是原来要删除数据的下一个数据,若是这个数是偶数那就继续删,是奇数就加加跳过,这样就能够不错过数据了。

五、构造

1、强制的默认构造写法

	vector() = default;//强制的默认构造

2、拷贝构造

		//拷贝构造
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto it : v)
			{
				push_back(it);
			}
		}

 3、赋值重载

		void clear()
		{
			_finish = _start;
		}
		void swap(const vector<T>& tmp)
		{
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_end_of_storage, tmp._end_of_storage);
		}
		//赋值重载(传统写法)
		vector<T>& operator=(const vector<T>& v)
		{
			if (_start)//说明被赋值的对象不是空,要先清除
			{
				clear();
			}
			reserve(v.capacity());
			for (auto it : v)
			{
				push_back(it);
			}
		}

		//赋值重载(现代写法)
		vector<T>& operator=(vector<T> tmp)
		{
			swap(tmp);
			return *this;
		}

a、传统写法:

若是原vector不为空,先置为空,之后再扩容,扩到和v一样的大小,再使用拷贝构造那一套,将v的数据一个一个尾插进入要被赋值的vector

b、现代写法:

传参时用tmp接收赋值等式右边的vector,进行拷贝构造,之后再和赋值等式左边的vector进行交换;(现代写法一般要在拷贝构造写好的情况下进行);

4、迭代器初始化

		//迭代器初始化
		template <class InputIterator>//可以接收不同的迭代器,但是迭代器里面的数据的类型要相同
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

这里使用到模板是为了接收各种类型的迭代器,但是值得注意的是这些迭代器的各自的容器里面的数据类型应该和此时的待构造的vector里面的数据保持一致,再不济也可以强制类型转换;

5、n个val去初始化

		//n个 val 去初始化
		vector(size_t n, const T& val = T())//不给值就用匿名对象,这个对象是 vector 里面的值
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

6、析构

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

六、容器打印函数模板中的注意事项:

	template <class T>
	void vector_print(const vector<T>& v)
	{
		//要写 typename,否则没有实例化的 vector<T> 不知道const_iterator是类型还是静态成员变量
		typename vector<T>:: const_iterator it = v.begin();
		//auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

这个函数模板定义在我们自己实现的vector类外面,用来打印vector,使用到迭代器,但是在写时注意这一行:

typename vector<T>:: const_iterator it = v.begin();

 要加上typename,否则编译器分不清这是类里面的静态成员变量还是类型,当然若是写成静态成员变量去使用,那就一定是静态成员变量,但是这里不能直接写,要在前面加上tyepname;第二种方法可以直接用auto自动生成类型去使用。

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

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

相关文章

某项目实战代码(一)

1.下载安装并配置环境变量openssl&#xff0c;可自行在网上寻找。 2.在项目中导入opensll中的“包含(include)”和“库(lib)” 3.选择debug x86平台&#xff0c;不然会报错。 4.运行结果如下。可自行比对 md5 value: 33b3bc8e05b4fcc16bd531dd9adac166 5.代码如下 #define…

基于STM32的智能家居灯光控制系统设计

引言 本项目将使用STM32微控制器实现一个智能家居灯光控制系统&#xff0c;能够通过按键、遥控器或无线模块远程控制家庭照明。该项目展示了如何结合STM32的外设功能&#xff0c;实现对灯光的智能化控制&#xff0c;提升家居生活的便利性和节能效果。 环境准备 1. 硬件设备 …

unix中的exec族函数介绍

一、前言 本文将介绍unix中exec族函数&#xff0c;包括其作用以及使用方法。当一个进程调用fork函数创建一个新进程后&#xff0c;新进程可以直接执行原本正文段的其他内容&#xff0c;但更多时候&#xff0c;我们在一个进程中调用fork创建新的进程后&#xff0c;希望新进程能…

在pycharm中设置后直接运行js代码

环境&#xff1a; pycharm专业版2020.3.2 已经安装nodejs和npm&#xff0c;并已经加入环境变量。 nodejs的安装参考在pycharm中运行js文件以及附加node.js下载步骤_python_脚本之家 下面开始在pycharm中设置&#xff0c;让其可以直接运行js代码&#xff0c;即需要安装一个叫…

C# 字符与字符串

本课要点&#xff1a; 1、字符类Char的使用 2、字符串类String的使用 3、可变字符串****StringBuilder 4、常见错误 一 何时用到字符与字符串 问题&#xff1a; 输出C#**课考试最高分&#xff1a;**98.5 输出最高分学生姓名&#xff1a;张三 输出最高分学生性别&#x…

六.应用层

目录 ​编辑 4.11 DHCP 6.1应用层的概述 6.2网络应用模型 CS P2P 6.3DNS 域名 www是什么&#xff0c;http是什么&#xff0c;到底什么区别&#xff1f; DNS出现及演化 DNS概括 DNS域名解析过程 6.4FTP FTP客户/服务器 FTP工作原理 FTP传输模式 6.5电子邮件 电…

King of Range 2024牛客国庆集训派对day3

原题 King of Range 解析 m 的值不大, 每次时间在 n logn 以内即可 我们遍历整个数组, 以 i 为右边界, 检测是否有满足条件的左边界, 一次只加上左面的所有可能, 用两个双向队列维护两个单调栈, 一个存最大值, 一个存最小值, 这样可以帮助找到合适的左边界 代码 #include …

JAVA并发编程系列(13)Future、FutureTask异步小王子

美团本地生活面试&#xff1a;模拟外卖订单处理&#xff0c;客户支付提交订单后&#xff0c;查询订单详情&#xff0c;后台需要查询店铺备餐进度、以及外卖员目前位置信息后再返回。 时间好快&#xff0c;一转眼不到一个月时间&#xff0c;已经完成分享synchronized、volatile、…

Linux应用——简易日志

1. 日志要求 对于一个日志来说&#xff0c;我们任认为其应该具有以下的内容 1. 日志时间 2. 日志等级 3. 日志内容 4. 文件名称与行号 在此基础上我们对不同的日志做出分级&#xff0c;即 info: 常规信息 warning: 报警信号 error: 严重信号&#xff0c;可能需要立…

UNIAPP 动态菜单实现方法

1. 封装tabbar组件&#xff0c;组件UI使用uview的tabbar allList 定义出全部的菜单 list 定义当前用户能看到的菜单使用 u-tabbar 渲染出来 list 2. 权限判断处理 3. 使用方式 在 tab 页&#xff0c;底部放入该 tab 组件&#xff0c;并设置当前回显的页面&#xff0c;这里使用…

STM32F407寄存器操作(DMA+I2C)

1.前言 因为后面需要用到大量基础通讯传输的问题&#xff0c;于是今天折腾了一下DMA传输I2C与SPI的效果&#xff0c;其实我先是把DMASPI搞出来了。但是考虑到网上对于STM32的I2C微词颇多&#xff0c;基础的协议都没有调试出来&#xff0c;更遑论DMA控制了&#xff0c;前面调不…

排序算法之——归并排序,计数排序

文章目录 前言一、归并排序1. 归并排序的思想2. 归并排序时间复杂度及空间复杂度3. 归并排序代码实现1&#xff09;递归版本2&#xff09;非递归版本 二、计数排序1. 计数排序的思想2. 计数排序的时间复杂度及空间复杂度3. 计数排序代码实现 总结&#xff08;排序算法稳定性&am…

计算机毕业设计 基于Python的无人超市管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候&#xff0c;效果是否会很好&#xff1f;在这篇论文中&#xff0c;作者给出了答案&#xff1a;Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

使用html写一个能发起请求的登录界面

目录 head部分 内联样式部分 body部分 login-form类的div myModal类的div id script部分 总的代码 界面与操作演示 <!DOCTYPE html> <html lang"en"> <!DOCTYPE html> 这是文档类型声明&#xff0c;告诉浏览器这是一个 HTML文档。 <…

jmeter学习(1)线程组与发送请求

1、线程组 执行顺序 &#xff1a;setUp线程组 > 线程组 > tearDown线程组 2、 发送请求 可以发送http、java、dubbo 请求等 下面讲解发送http 1&#xff09;Http请求默认值 作用范围是该线程组下的所有HTTP请求&#xff0c;如果http请求设置的与默认值冲突&#xff0…

前端规范工程-3:CSS规范(Stylelint)

样式规范工具&#xff08;StyleLint&#xff09; Stylelint 是一个灵活且强大的工具&#xff0c;适用于保持 CSS 代码的质量和一致性。结合其他工具&#xff08;如 Prettier 和 ESLint&#xff09;&#xff0c;可以更全面地保障前端代码的整洁性和可维护性。 目录 样式规范工具…

oracle virtualBox 拖动文件到虚拟机内报错

DnD: Error: Drag and drop to guest not possible -- either the guest OS does not support this, or the.... 首先将拖放的双向选项打开 打开CD驱动器 根据操作系统是32还是64安装对应的安装包&#xff0c;amd64为64位系统&#xff0c;x86为32位系统 安装后重启即可向虚拟机…

【C语言】数组练习

【C语言】数组练习 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚练习2、二分查找 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 练习2、二分查找 在⼀个升序的数组中查找指…

sql语句牛客练习

文章目录 1. SQL21 浙江大学用户题目回答情况① 错误② 正确 2. SQL22 统计每个学校的答过题的用户的平均答题数① 错误② 正确 3. SQL23 统计每个学校各难度的用户平均刷题数4. SQL25 查找山东大学或者性别为男生的信息① 错误② 正确 5. SQL26 计算25岁以上和以下的用户数量①…