【C++】---STL之vector的模拟实现

【C++】---STL之vector的模拟实现

  • 一、vector在源码中的结构:
  • 二、vector类的实现:
    • 1、vector的构造
    • 2、析构
    • 3、拷贝构造
    • 4、赋值运算符重载
    • 5、迭代器
    • 6、operator[ ]
    • 7、size()
    • 8、capacity()
    • 9、reserve()
    • 10、resize()
    • 11、empty()
    • 12、push_back()
    • 13、pop_back()
    • 14、insert()
    • 15、erase()

一、vector在源码中的结构:

在这里插入图片描述
vector的成员变量:
start,finish,end_of_storage的返回类型都是迭代器!
在这里插入图片描述
他的成员变量是三个迭代器:
(1)start:指向第1个元素(开始)
(2)finish:指向数据的结束
(3)end_of_storage:指向空间的结束

二、vector类的实现:

实现以下vector相关内容:
在这里插入图片描述

1、vector的构造

1、无参数构造,构造出一个空容器:

//1、构造函数:
		vector()// 空的构造函数,所有变量都初始化为空!
			:_start(nullptr)
			,_finish(nullptr)
			, _end_of_storage(nullptr)
		{

		}

2、填充构造函数 向容器中插入n个值为val的元素

// 1.1 填充构造函数 向容器中插入n个值为val的元素
		vector(size_t n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			while (n)
			{
				push_back(val);
				n--;
			}
		}

3、完整代码:

#include<iostream>
#include<string>

using namespace std;

namespace yjl
{
	template<class T>
	class vector
	{
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	public:
		//1、构造函数:
		vector()// 空的构造函数,所有变量都初始化为空!
			:_start(nullptr)
			,_finish(nullptr)
			, _end_of_storage(nullptr)
		{

		}

		// 1.1 填充构造函数 向容器中插入n个值为val的元素
		vector(size_t n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			while (n)
			{
				push_back(val);
				n--;
			}
		}

	};
}

2、析构

// 2、析构
		~vector()
		{
			if (_start)
			{
				delete[] _start;// 释放空间!
			}
			_start = _finish = _end_of_storage = nullptr;
			// 对vector的三个迭代器置空!
		}

3、拷贝构造

1、传统的写法:

注意: 当我们不写vector的拷贝构造函数,编译器会自动生成一份,但编译器生成的只能完成数据的浅拷贝,然而vector的三个成员变量都是迭代器,也就是指针T*,如果T是内置类型,那么就不会出现问题,但是如果T是自定类型,会出现拷贝对象和被拷贝对象指向同一块空间,根据后定义的先析构的原则,此程序会析构两次,会导致程序崩溃

	// 3、拷贝构造:
		// (1)传统写法:v1(v)
		vector(const vector<T>& v)
		{
			// 1.申请空间
			_start = new T[v.capacity()];
			// 2.拷贝数据
			for (int i=0;i<v.size();i++)
			{
				_start[i] = v._start[i];
			}
			// 3.更新数据
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

2、现代的拷贝构造:开空间+逐个尾插

使用现代的拷贝构造时必须初始化,否则_start、_finish、_end_of_storage都是随机值,拷贝数据时可能会导致越界。如果T是自定义类型,那么会调用T的拷贝构造函数进行深拷贝

//(2)现代写法:复用
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			// 1.先开空间:reserve
			reserve(v.capacity());
			// 2.逐个尾插
			for (auto& e : v)
			{
				push_back(e);
			}
		}

4、赋值运算符重载

1、传统的赋值运算符重载:

	// 4、赋值运算符重载
		// (1)传统写法:
		vector<T> operator=(vector<T> v)
		{
			if (this != v)
			{
				delete[] _start;// 如果赋值运算符(=),左右两边不相等,我直接把调用该函数的this清空,
				//然后把右边的值赋给左边,后面的代码相当于前面的拷贝构造

				// 1.申请空间
				_start = new T[v.capacityu()];
				// 2.拷贝数据
				for (size_t i = 0; i < v.size(); i++)
				{
					_start = v._start[i];
				}
				// 3.更新数据
				_finish = _start + v.size();
				_end_of_storage = _start + v.capacity();
			}
			
		}

2、现代写法:

		// (2)现代写法:
		// v1 = v2  (在成员函数中,v1调用operator=,v1就是this指针,v2相当于 v )

		vector<T>& operator=(vector<T> v)//这里面的参数故意没有传引用,目的就是不改变原来v2的值!!!
		{
			swap(v);//swap是库里的的成员函数,有隐含的this指针!(即:v1 )
			return *this;
		}
		viod swap(vector<T> v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

5、迭代器

(1)普通迭代器

iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}

(2)const迭代器


		const iterator begin()const
		{
			return _start;
		}
		const iterator end()const
		{
			return _finish;
		}

6、operator[ ]

//6、 operator[ ]

		T& operator[](size_t i)
		{
			assert(i < size());

			return _start[i];
		}


		const T& operator[](size_t i)const
		{
			assert(i < size());

			return _start[i];
		}

7、size()

// 7、size()
		size_t size()
		{
			return _finish - _start;
		}

8、capacity()

// 8、capacity()

		size_t capacity()
		{
			return _end_of_storage - _start;
		}

9、reserve()

1.开辟新空间

2.拷贝数据

3.释放旧空间

4.更新数据

	// 9.reserve
		void reserve(size_t n)
		{
			size_t old_size = size();

			// 1.开辟新空间
			T* tmp = new T[n];
			// 2.拷贝数据
			for (size_t i = 0; i < old_size; i++)
			{
				tmp[i] = _start[i];
			}
			// 3.释放旧空间
			delete[] _start;
			// 4.更新数据

			_start = tmp;
			_finish = _start + old_size;
			_end_of_storage = _start + n;
		}

为啥要事先保存一个old_size???

注意:错误案例:
在这里插入图片描述
在这里插入图片描述

10、resize()

(1)当resize的大小比原来小,说明空间够,只需要修改大小即可

(2)当resize的大小比原来大,说明空间不够,同时也说明容量可能不够,要判断是否需要申请容量


		// 10、resize()
		void resize(size_t n, const T&  val= T())// 匿名函数的使用:
		{
			if (n > size())
			{
				// 扩容:
				reserve(n);

				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

11、empty()

// 11、empty
		bool empty()
		{
			return _start == _finish;//起始空间是否为结束空间
		}

12、push_back()

尾插时,需要:

(1)判断增容

(2)赋值

(3)更新大小


		// 12、push_back()
		void push_back(const T& val)
		{
			// 1、判断是否需要扩容
			if (_finish == _end_of_storage)
			{
				int newcapacity = capacity()==0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			// 2.赋值:
			*_finish = val;
			// 3.更新数据:
			_finish++;

		}

13、pop_back()

尾删:

(1)判空

(2)直接更新大小

// 13、pop_back
		void pop_back()
		{
			assert(!empty());
			_finish--;
		}

14、insert()

1、先断言要插入的pos的位置要在start和finish范围之内。

2、然后再判断是否需要扩容,如果进行了扩容,那么就需要先保存要插入的pos位置到start之间的相对位置。(因为如果进行了扩容操作,那么原来的start指针就会被销毁,所以说要先保存相对位置。)

3、定义一个指针,从尾部往前遍历直到post位置的下一个元素,依次往后挪动。

// 14、insert
		void insert(iterator pos, const T& val)
		{
			// 1、先断言
			assert(pos >= _start);
			assert(pos <= _finish);

			// 2、判断是否需要扩容
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;

				reserve(capacity() == 0 ? 4 : capacity() * 2);

				pos = _start + len;
			}
			// 3、将要插入pos的位置,后面的数据依次往后挪。
			iterator it = _finish - 1;
			while (it >= pos)
			{
				*(it + 1) = *it;
				it--;
			}
			_start[pos] = val;
			_finish++;
		}

15、erase()

1、先断言检查要删除位置pos是否在start和finish之内。

2、然后再要删除位置的下一个位置定义一个指针,然后从这个指针到结尾的位置的数据依次往前挪动覆盖数据。

3、_finish- -

// 15、erase
		void erase(iterator pos)
		{
			//1、先断言
			assert(pos >= _start);
			assert(pos <= _finish);
			//2、定义挪动数据的指针
			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it-1) = *it ;
				it++;
			}
			_finish--;
		}

好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

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

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

相关文章

如何在PostgreSQL中设置自动清理过期数据的策略

文章目录 方法一&#xff1a;使用临时表和定期清理步骤&#xff1a;示例代码&#xff1a;创建临时表&#xff1a;定期清理脚本&#xff08;bash psql&#xff09;&#xff1a; 方法二&#xff1a;使用分区表和定期清理步骤&#xff1a;示例代码&#xff1a;创建分区表&#xf…

《内向者优势》:不要低估一个内向的人

#世界读书日 作者主页&#xff1a; &#x1f517;进朱者赤的博客 精选专栏&#xff1a;&#x1f517;经典算法 作者简介&#xff1a;阿里非典型程序员一枚 &#xff0c;记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; ❤…

Redis篇:缓存更新策略最佳实践

前景&#xff1a; 缓存更新是redis为了节约内存而设计出来的一个东西&#xff0c;主要是因为内存数据宝贵&#xff0c;当我们向redis插入太多数据&#xff0c;此时就可能会导致缓存中的数据过多&#xff0c;所以redis会对部分数据进行更新&#xff0c;或者把他叫为淘汰更合适&a…

Vue3的监听属性watch和计算属性computed

监听属性watch 计算属性computed 一、监听属性watch watch 的第一个参数可以是不同形式的“数据源&#xff0c;watch 可以监听以下几种数据&#xff1a; 一个 ref (包括计算属性)、 一个响应式对象、 一个 getter 函数、 或多个数据源组成的数组 watch 的参数:监视的回调&…

代码随想录算法训练营第三十五天|860.柠檬水找零、406.根据身高重建队列、452. 用最少数量的箭引爆气球

860. 柠檬水找零 思路&#xff1a; 只需要维护三种金额的数量&#xff0c;5&#xff0c;10和20。 有如下三种情况&#xff1a; 情况一&#xff1a;账单是5&#xff0c;直接收下。情况二&#xff1a;账单是10&#xff0c;消耗一个5&#xff0c;增加一个10情况三&#xff1a;…

好友关注-实现分页查询收邮箱

9.5好友关注-实现分页查询收邮箱 需求&#xff1a;在个人主页的“关注”卡片中&#xff0c;查询并展示推送的Blog信息&#xff1a; 具体操作如下&#xff1a; 1、每次查询完成后&#xff0c;我们要分析出查询出数据的最小时间戳&#xff0c;这个值会作为下一次查询的条件 2…

考研党打印资料怎么使用云打印服务?

对于准备考研的同学们来说&#xff0c;在备考的时候需要准备许多资料&#xff0c;这些资料的打印费用成为了考研党的巨额支出。那么在生活费有限的情况下&#xff0c;考研党打印资料最好是选择云打印服务&#xff0c;因为易绘创云打印服务低至5分钱/页还包邮。那么考研党打印资…

Pytest精通指南(28)钩子函数-测试报告(pytest-html)

文章目录 前言应用场景插件安装参数分析使用方法拓展-定制化报告 前言 在软件开发过程中&#xff0c;测试是确保代码质量的关键环节。 而测试报告则是测试过程中不可或缺的输出物&#xff0c;它为我们提供了关于测试用例执行情况的详细信息&#xff0c;帮助我们快速定位和解决问…

服务器(AIX、Linux、UNIX)性能监视器工具【nmon】使用介绍

目录 ■nmon简介 1.安装 2.使用简介 3.使用&#xff08;具体使用的例子【CPU】【内存】&#xff09; 4.采集数据 5.查看log&#xff08;根据结果&#xff0c;生成报表&#xff09; 6.分析结果 ■nmon简介 nmon&#xff08;"Nigels performance Monitor"&…

比特币成长的代价

作者&#xff1a;Jeffrey Tucker&#xff0c;作家和总裁。曾就经济、技术、社会哲学和文化等话题广泛发表演讲。编译&#xff1a;秦晋 2017 年之后参与比特币市场的人遇到了与之前的人不同的操作和理想。如今&#xff0c;没有人会太在意之前的事情&#xff0c;说的是 2010-2016…

SL3038 耐压150V恒压芯片 60V降24V 72V降12V降压IC

SL3038 是一款恒压芯片&#xff0c;其耐压值为 150V。这意味着它可以在高达 150V 的电压下工作而不会损坏。现在&#xff0c;让我们来讨论您提到的两个降压应用&#xff1a;从 60V 降到 24V 和从 72V 降到 12V。 1. 60V 降到 24V&#xff1a; 输入电压&#xff1a;60V 输出电…

02 IO口的操作

文章目录 前言一、IO的概念1.IO接口2.IO端口 二、CPU和外设进行数据传输的方法1.程序控制方式1.1 无条件1.2 查询方式 2.中断方式3.DMA方式 一、方法介绍和代码编写1.前置知识2.程序方式1.1 无条件方式1.1.1 打开对应的GPIO口1.1.2 初始化对应的GPIO引脚1.1.2.1 推挽输出1.1.2.…

【Hadoop】-Hive部署[12]

目录 思考 VMware虚拟机部署 规划 步骤1&#xff1a;安装MySQL数据库 步骤2&#xff1a;配置Hadoop 步骤3&#xff1a;下载解压Hive 步骤4&#xff1a;提供MySQL Driver包 步骤5&#xff1a;配置Hive 步骤6&#xff1a;初始化元数据库 步骤7&#xff1a;启动Hive&…

TDSQL同一个所属Set显示3个备份节点

欢迎关注“数据库运维之道”公众号&#xff0c;一起学习数据库技术! 本期将为大家分享《TDSQL同一个所属Set显示3个备份节点》的处置案例。 关键词&#xff1a;分布式数据库、TDSQL、备份节点 1、问题描述 登录赤兔管理平台&#xff0c;单击左侧导航栏“实例管理/集群管理”…

漫谈-AI 时代的信息模型

模型化- 数字化转型的重要基石 在各行各业推行数字化转型过程中&#xff0c;构建信息化模型十分重要&#xff0c;它是数字化转型的基石。事实上&#xff0c;数字化转型的核心是“万物皆模型”&#xff0c;在工业领域&#xff0c;以德国为主导的工业4.0 发展进程中&#xff0c;…

Access denied for user ‘zabbix‘@‘localhost‘ (using password: NO)

现象 排查过程 进入数据库show grants for zabbixlocalhost;select host,user from mysql.user;cat /etc/zabbix/zabbix_server.conf | grep DB | grep -vE ‘#|$’cat /etc/zabbix/web/zabbix.conf.php | grep DB 解决办法 mysql 8.0以下 DPassword123.com mariadb -e "…

java多线程-并发和并行

进程 并发 进程中的线程是由CPU进行调度的&#xff0c;但是CPU能够处理的进程数量有限为了保证所有的线程都在运行&#xff0c;CPU会快速切换&#xff0c;给外界的感觉就是所有的线程都在运行&#xff0c;这就是并发。 并行

【力扣 Hot100 | 第六天】4.21(最长连续序列)

文章目录 10.最长连续序列10.1题目10.2解法&#xff1a;哈希法10.2.1哈希思路10.2.2代码实现 10.最长连续序列 10.1题目 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时…

php 编译安装oracel扩展

第一步安装Oracle客户端 1&#xff0c;需要下载基础包和sdk oracle客户端下载链接&#xff1a;Oracle Instant Client Downloads for Linux x86-64 (64-bit) https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html 选择最新版本 versi…

国产PLC有哪些,哪个牌子比较好用?

你知道国产PLC有哪些吗,哪个牌子更好用吗&#xff1f; 今天拿出国产先锋的汇川与台达对比&#xff0c;注&#xff1a;视频后方有各品牌学习资料免费送&#xff0c;需要的移步自取。话说回来&#xff0c;只要基于Codesys开发的都比较好用&#xff0c;只是使用底层芯片不同&…