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

news2024/10/6 4:02:30

【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--;
		}

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

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

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

相关文章

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结 在深度学习中&#xff0c;优化器的目标是通过调整模型的参数&#xff0c;最小化&#xff08;或最大化&#xff09;一个损失函数。 优化器使用梯度下降等迭代方法来更新模型的参数&#xff0c;以使损失函数…

【JavaScriptthreejs】对于二维平面内的路径进行扩张或缩放

目标 对指定路径 [{x,y,z},{x,y,z},{x,y,z},{x,y,z}.........]沿着边缘向内或向外扩张&#xff0c;达到放大或缩小一定范围的效果&#xff0c;这里我们获取每个点&#xff08;这里是Vector3(x,y,z)&#xff09;,获取前后两个点和当前点的坐标&#xff0c;计算前后两点的向量&a…

AJAX——案例

1.商品分类 需求&#xff1a;尽可能同时展示所有商品分类到页面上 步骤&#xff1a; 获取所有的一级分类数据遍历id&#xff0c;创建获取二级分类请求合并所有二级分类Promise对象等待同时成功后&#xff0c;渲染页面 index.html代码 <!DOCTYPE html> <html lang&qu…

【数据库】MongoDB

文章目录 [toc]数据库操作查询数据库切换数据库查询当前数据库删除数据库查询数据库版本 数据集合操作创建数据集合查询数据集合删除数据集合 数据插入插入id重复的数据 数据更新数据更新一条丢失其他字段保留其他字段 数据批量更新 数据删除数据删除一条数据批量删除 数据查询…

S-Edge网关:柔性部署,让物联网接入更统一

S-Edge网关是什么&#xff1f; 网关是在实际物理世界与虚拟网络世界相连接的交叉点&#xff0c;为了让这个交叉点尽可能的复用&#xff0c;无需每种设备都配套一种连接方式&#xff0c;边缘网关主要就是用于传感器等物理设备与网络实现数据交互的通用设备&#xff0c;也称为物…

跨部门协作中的沟通困境与平台建设策略——以软硬件研发为例

一、背景 在科技行业&#xff0c;跨部门合作的重要性不言而喻&#xff0c;然而实际工作中&#xff0c;经常会遭遇沟通不畅的现象。以软件与硬件研发部门为例&#xff0c;两者在产品研发过程中经常需要紧密协作&#xff0c;但却时常出现信息传递障碍。当你试图阐述观点时&#…

SpringCloud系列(11)--将微服务注册进Eureka集群

前言&#xff1a;在上一章节中我们介绍并成功搭建了Eureka集群&#xff0c;本章节则介绍如何把微服务注册进Eureka集群&#xff0c;使服务达到高可用的目的 Eureka架构原理图 1、分别修改consumer-order80模块和provider-payment8001模块的application.yml文件&#xff0c;使这…

pnpm 安装后 node_modules 是什么结构?为什么 webpack 不识别 pnpm 安装的包?

本篇研究&#xff1a;使用 pnpm 安装依赖时&#xff0c;node_modules 下是什么结构 回顾 npm3 之前&#xff1a;依赖树 缺点&#xff1a; frequently packages were creating too deep dependency trees, which caused long directory paths issue on Windowspackages were c…

Linux(韦东山)

linux和windows的差别 推荐学习路线 先学习 应用程序 然后&#xff1a; 驱动程序基础 最后&#xff1a;项目 韦东山课程学习顺序 看完第六篇之后&#xff0c;还可以继续做更多的官网的项目 入门之后&#xff0c;根据自己的需要学习bootloader / 驱动大全 / LVGL

微信小程序实时日志使用,setFilterMsg用法

实时日志 背景 为帮助小程序开发者快捷地排查小程序漏洞、定位问题&#xff0c;我们推出了实时日志功能。开发者可通过提供的接口打印日志&#xff0c;日志汇聚并实时上报到小程序后台。开发者可从We分析“性能质量->实时日志->小程序日志”进入小程序端日志查询页面&am…

数据结构(学习笔记)王道

一、绪论 1.1 数据结构的基本概念 数据&#xff1a;是信息的载体&#xff0c;是描述客观事物属性的数、字符以及所有输入到计算机中并被计算机程序识别和处理的符号的集合。&#xff08;计算机程序加工的原料&#xff09;数据元素&#xff1a;数据的基本单位&#xff0c;由若干…

【深度学习】烟雾和火焰数据集,野外数据集,超大量数据集,目标检测,YOLOv5

标注了2w张数据集&#xff0c;是目标检测yolo格式的&#xff0c;有火焰、烟雾两个目标&#xff0c;下图是训练时候的样子&#xff1a; 训练方法看这里&#xff1a; https://qq742971636.blog.csdn.net/article/details/138097481 数据集介绍 都是博主辛苦整理和标注的&…

8.0MGR单主模式搭建_克隆(clone)插件方式

为了应对事务一致性要求很高的系统对高可用数据库系统的要求&#xff0c;并且增强高可用集群的自管理能力&#xff0c;避免节点故障后的failover需要人工干预或其它辅助工具干预&#xff0c;MySQL5.7新引入了Group Replication&#xff0c;用于搭建更高事务一致性的高可用数据库…

配置网络设备的密码设置以及忘记密码的恢复方式以及实现全网互通

1.实验拓扑图&#xff1a; 2.实验需求&#xff1a; 1.推荐步骤 1.1配置IP&#xff1a; 不过多说了&#xff0c;较为基础&#xff08;略&#xff09; 2.推荐步骤 2.所有网络设备配置console接口密码 首先进入全局模式&#xff0c;输入以下代码(进入接口console接口0给其配置密…

HTTP慢连接攻击的原理和防范措施

随着互联网的快速发展&#xff0c;网络安全问题日益凸显&#xff0c;网络攻击事件频繁发生。其中&#xff0c;HTTP慢速攻击作为一种隐蔽且高效的攻击方式&#xff0c;近年来逐渐出现的越来越多。 为了防范这些网络攻击&#xff0c;我们需要先了解这些攻击情况&#xff0c;这样…

html--canvas粒子球

<!doctype html> <html> <head> <meta charset"utf-8"> <title>canvas粒子球</title><link type"text/css" href"css/style.css" rel"stylesheet" /></head> <body><script…

TDSQL手动调整备份节点或冷备节点

一、背景描述 近期TDSQL数据库备份不稳定&#xff0c;有些set实例的备份任务未自动发起。经排查分析&#xff0c;存在多个set实例容量已经超过TB级别&#xff0c;且冷备节点都是同一台。因此&#xff0c;需要手动将当前备份节点改到其他备节点&#xff0c;开启增量备份&#x…

杰理695的UI模式LED灯控制

UI模式LED灯修改每个模式对应的LED灯闪烁修改在ui_normal_status_deal(u8 *status, u8 *power_status, u8 ui_mg_para)

运维 kubernetes(k8s)基础学习

一、容器相关 1、发展历程&#xff1a;主机–虚拟机–容器 主机类似别墅的概念&#xff0c;一个地基上盖的房子只属于一个人家&#xff0c;很多房子会空出来&#xff0c;资源比较空闲浪费。 虚拟机类似楼房&#xff0c;一个地基上盖的楼房住着很多人家&#xff0c;相对主机模式…

easyx库的学习(鼠标信息)

前言 本次博客是作为介绍easyx库的使用&#xff0c;最好是直接代码打到底&#xff0c;然后看效果即可 代码 int main() {initgraph(640, 480, EX_SHOWCONSOLE|EX_DBLCLKS);setbkcolor(RGB(231, 114, 227));cleardevice();//定义消息结构体ExMessage msg { 0 };//获取消息wh…