[STL]详解vector模拟实现

news2024/11/15 17:45:00

[STL]vector模拟实现

文章目录

  • [STL]vector模拟实现
    • 1. 整体结构总览
    • 2. 成员变量解析
    • 3. 默认成员函数
      • 构造函数1
      • 构造函数2
      • 构造函数3
      • 拷贝构造函数
      • 析构函数
    • 4. 迭代器相关函数
      • begin函数
      • end函数
      • begin函数const版本
      • end函数const版本
    • 5.容量相关函数
      • size函数
      • capacity函数
      • reserve函数
      • resize函数
      • empty函数
    • 6. 数据修改函数
      • push_back函数
      • pop_back函数
      • insert函数
      • erase函数
    • 7. 数据访问函数
      • []运算符重载
      • []运算符重载const版本
      • =运算符重载const版本
    • 8. 完整源码链接

1. 整体结构总览

template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector(); //默认构造函数
		vector(size_t n, const T& val = T());
		vector(int n, const T& val = T());
		template <class InputIterator>
		vector(InputIterator first, InputIterator last); //迭代器初始化
		vector(const vector<T>& v);//拷贝构造
		~vector();//析构函数
		
		iterator begin();//返回指向数据开头的迭代器
		iterator end(); //返回指向最后一个数据的下一个数据的位置的迭代器
		const_iterator begin() const;//const迭代器版本
		const_iterator end() const;//const迭代器版本

		size_t size()const; //获取容器内有效数据的个数
		size_t capacity()const;//获取容器容量
		void reserve(size_t n); //扩容函数
		void resize(size_t n, T val = T());//扩容函数
		bool empty();//检查容器是否为空

		void push_back(const T& x); //尾插函数
		void pop_back();//尾删函数
		iterator insert(iterator pos, const T& val);//插入函数
		iterator erase(iterator pos);//删除函数

		T& operator[](size_t n); //[]重载
		const T& operator[](size_t n) const; //const变量版本[]重载
		vector<T>& operator=(const vector<T>& v);//传统写法
		//vector<T>& operator=(vector<T> v);//现代写法

		void swap(vector<T>& v);//交换函数

	private:
		iterator _start;  //指向数据开头的位置
		iterator _finish; //指向最后一个有效数据的下一个数据的位置
		iterator _end_of_storage; //指向容量空间的末尾位置
	};

2. 成员变量解析

_start 指向数据的开始位置,_finish指向最后一个有效数据的后一个数据的位置,_end_of_storage指向容量空间的末尾位置。3.

3. 默认成员函数

构造函数1

首先是无参数的默认的构造函数,构造的是一个空容器,因此成员变量赋空值就行。

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

构造函数2

此构造函数实现的功能是在定义时开一定空间,并且将这段空间全是相同的数据,由于模拟实现这个构造函数复用了其他函数,要注意一定要初始化变量,否则会影响正常复用其他函数。

vector(size_t n, const T& val = T())//const 引用可以延长匿名对象的寿命,使该匿名对象的寿命和引用相当
	: _start(nullptr), 
	_finish(nullptr),
	_end_of_storage(nullptr)
{
	reserve(n);//提前扩容,提高效率
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}
vector(int n, const T& val = T())//重载一个版本,兼容更多类型
	: _start(nullptr), 
	_finish(nullptr),
	_end_of_storage(nullptr)
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

构造函数3

此构造函数的功能是利用不同类型迭代器初始化容器,只要迭代器指向数据和容器数据类型能匹配上就能使用该函数,包括指向数组的原生指针可看作迭代器,可以作为参数传入。

template <class InputIterator>
vector(InputIterator first, InputIterator last)//指向数组的原生指针可看作迭代器,可以作为参数传入
	: _start(nullptr),
	_finish(nullptr),
	_end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

拷贝构造函数

拷贝构造函数的实现需要注意深浅拷贝的问题,首先要注意自身空间需要深拷贝,拷贝构造构造容器时为自身需要申请独立的空间,然后将数据进行拷贝,而不是将相应成员变量(_start等成员变量)直接赋值,两个容器指向同一块空间,造成问题。

image-20230616211124792

其次要注意的是如果容器存储的是需要申请空间的自定义类型,在拷贝构造时,也需要单独为它们申请空间然后拷贝数据,而不是将要拷贝的容器的数据直接赋值过来,比如存储string类,直接赋值会导致两个容器存储的string对象指向相同的空间。

image-20230616212300898

传统写法:

申请新的空间然后将数据一个一个拷贝过来。

vector(const vector<T>& v)
{
	//传统写法
	_start = new T[v.capacity()]; //为容器申请独立空间
	for (size_t i = 0; i < v.size(); ++i)
	{
		_start[i] = v._start[i];//如果是内置类型直接赋值,如果是自定义类型,自定义类型会重载=运算符,满足深拷贝的需求,比如string类的=运算符就是对数据进行深拷贝
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

现代写法:

创建一个新的容器拷贝数据,然后将该容器的空间转让过来。

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}
vector(const vector<T>& v)
	: _start(nullptr),
	_finish(nullptr),
	_end_of_storage(nullptr)
{
	//现代写法
	vector<T> tmp(v.begin(), v.end());//用迭代器初始化创建一个和v数据相同的vector
	swap(tmp);
}

另外值得注意的是由于是利用自定义类型会重载=运算符,满足深拷贝的需求,因此需要实现=运算符重载,并且是对数据深拷贝,否则使用vector<vector<T>>类型时也会因为浅拷贝出现问题。

析构函数

由于空间是new出来的连续空间,因此只需要用delete关键字释放就可以,然后将各成员变量置空。

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

4. 迭代器相关函数

begin函数

begin函数的功能是返回指向数据开始位置的迭代器,因此只需要返回_start变量就可以。

iterator begin()
{
	return _start;
}

end函数

end函数的功能是返回指向最后一个有效数据的下一个数据的位置,因此只需要返回_finish变量就可以。

iterator end()
{
	return _finish;
}

begin函数const版本

const版本的begin函数是提供给const类型的容器使用,因此this指针加const才能适配,并且返回值得是const版本的迭代器。

const_iterator begin() const
{
	return _start;
}

end函数const版本

和const版本的begin函数同理,this指针加const,并且返回值是const版本的迭代器。

const_iterator end() const
{
	return _finish;
}

5.容量相关函数

size函数

由于申请的空间是连续的,因此只需要用_finish减_start就可以得到容器内有效数据的个数。

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

capacity函数

和size函数原理相同,只需要用_end_of_storage减_start即可获取容器的容量。

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

reserve函数

reserve规则

1. 如果n小于capacity,什么都不做。
1. 如果n大于capacity,就将capacity扩容到n。
void reserve(size_t n)
{
	if (n > capacity())
		{
			T* tmp = new T[n]; //申请能存储n个T类型数据的空间
			size_t sz = size();//提前保存原有size大小,后续size函数会失效
			if (_start) //如果原有空间存在,需要拷贝原有空间的数据
			{
				for (size_t i = 0; i < sz; ++i)
				{
					tmp[i] = _start[i];//如果容器存储的是自定义类型,使用=运算符,利用自定义类型自身的实现,避免浅拷贝
				}
				delete[] _start; //将原有空间释放
			}
			_start = tmp;//指向新空间的数据开头
			_finish = _start + sz; //指向新空间最后一个有效数据的下一个位置
			_end_of_storage = _start + n;//指向新空间的末尾位置
		}
}

注意:原有有效数据个数需在_start指向修改前保存,否则由于_start指向的改变导致size函数的结果为错误的,进而导致_finish指向的位置不正确。

image-20230615151119279

resize函数

resize规则:

1. 当n小于当前的size时,将size缩小到n。
 1. 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。

void (size_t n, T val = T())
{
	if (n < size())//n是无符号整形,不用担心n为负数造成_finish指向位置为非法位置
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())//n大于capacity需要扩容
		{
			reserve(n);
		}
        while (_finish != _start + n)//将原有有效数据位置到第n个数据位置设置为val
		{
			*_finish = val;
			++_finish;
		}
	}
}

注意: 由于需要兼容模板, 因此在C++当中内置类型也可以看作是一个类,它们也有自己的默认构造函数,所以在给resize函数的参数val设置缺省值时,设置为T( )即可。

empty函数

empty函数的作用是检查容器是否为空,当_start和_finish指向的位置相同时就是空。

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

image-20230615153442627

6. 数据修改函数

push_back函数

容器已满就先扩容再尾插,容器未满就直接尾插,由于_finish指向的是最后一个有效数据的下一个数据的位置,因此只需要对其解引用就可以尾插数据。

void push_back(const T& x)
{
	if (_finish == _end_of_storage) //容器已满,需要扩容
	{
		size_t newcapacity = capacity == 0 ? 4 : capacity() * 2; //扩容空间至原有空间的2倍
		reserve(newcapacity);
	}
	//容器未满,直接尾插
	*_finish = x;
	++_finish;
}

pop_back函数

删除数据前要保证有数据,因此断言判断容器不为空,删除数据只需要将_finish的指向改变就可以。

void vector<T>::pop_back()
{
	assert(!empty());//检查是否有数据
	--_finish;
}

image-20230615154321112

insert函数

insert函数的功能是将数据插入到迭代器指向的位置,因此迭代器的指向的正确性是很重要的。首先要断言判断插入位置是否越界;其次要注意容器是否有空间插入数据,因为如果空间不够,需要利用申请新的空间的方法来实现扩容,申请新的空间,会导致形参传入的指向插入数据位置的迭代器失效,具体讲就是迭代器指向原有空间,而原有空间已经释放了,导致迭代器失效,因此在容量不足时要单独判断,并且保证迭代器指向正确的位置才能完成正确插入的。由于空间不论怎么变,指向的数据和数据开头的偏移量是不变的,因此需要利用偏移量来保证迭代器指向位置的正确即可,保证迭代器指向正确后,只需要挪动数据,将要插入的数据写入正确位置即可。返回插入位置的迭代器。

image-20230615205803223

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);//检查插入位置是否越界
	assert(pos <= _finish);
	size_t len = pos - _start;//记录偏移量
	if (_finish == _start)//容器已满
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
		pos = _start + len;//使得pos指向新空间的相应位置
	}
	iterator end = _finish - 1;
	while (pos <= end)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
	return pos;
}

erase函数

删除数据首先要确保删除的数据是有效数据,因此断言判断迭代器指向的位置,然后只需要将删除数据后的数据向前挪动覆盖即可,最后别忘了将指向最后一个有效数据的下一个位置的_finish修正。

iterator erase(iterator pos)
{
	assert(pos >= _start);//判断删除位置是否越界
	assert(pos < _finish);
	iterator start = pos + 1;
	while (start != _finish)
	{
		*(start - 1) = *start;
		++start;
	}
	--_finish;
	return pos;
}

7. 数据访问函数

[]运算符重载

申请的是连续的空间,因此只需要利用偏移量找到对应的位置解引用即可,由于[]需要支持修改数据内容,返回类型必须是引用。

T& operator[](size_t n)
{
    assert(n < size());//检查是否越界访问
	return _start[n];
}

[]运算符重载const版本

const版本的[]运算符重载是提供给const类型的容器使用,因此this指针加const才能适配,并且返回值得是const类型的数据。

const T& operator[](size_t n) const
{
	return _start[n];
}

=运算符重载const版本

传统写法:

申请相应大小的新的空间然后将数据依次拷贝。

vector<T>& operator=(const vector<T>& v)
{
	if (_start != v._start) //避免v1 = v1出错
	{
		delete[] _start;
		_start = new T[v.capacity()];
		for (size_t i = 0; i < v.size(); ++i)
		{
			_start[i] = v._start[i];
		}
		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
	}
	return *this;
}

现代写法:

利用形参会拷贝构造实参,然后将形参的空间交换过来。

vector<T>&operator=(vector<T> v)
{
	//现代写法
	swap(v);
	return *this;
}

8. 完整源码链接

STL/vector/vector/vector.h · 钱雪明/日常代码 - 码云 - 开源中国 (gitee.com)

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

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

相关文章

【Nodejs】操作mongodb数据库

1.简介 Mongoose是一个让我们可以通过Node来操作MongoDB的模块。Mongoose是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装&#xff0c;并提供了更多的功能。在大多数情况下&#xff0c;它被用来把结构化的模式应用到一个MongoDB集合&#xff0c;并…

【每日一题】2500. 删除每行中的最大值

【每日一题】2500. 删除每行中的最大值 2500. 删除每行中的最大值题目描述解题思路 2500. 删除每行中的最大值 题目描述 给你一个 m x n 大小的矩阵 grid &#xff0c;由若干正整数组成。 执行下述操作&#xff0c;直到 grid 变为空矩阵&#xff1a; 从每一行删除值最大的元…

阿里Java开发手册~建表规约

1. 【强制】表达是与否概念的字段&#xff0c;必须使用 is _ xxx 的方式命名&#xff0c;数据类型是 unsigned tinyint &#xff08; 1 表示是&#xff0c; 0 表示否 &#xff09; 。 说明&#xff1a; 任何字段如果为非负数&#xff0c;必须是 unsigned 。 正例&am…

Jenkins+Docker+Docker-Compose自动部署,SpringCloud架构公共包一个任务配置

前言 Jenkins和docker的安装&#xff0c;随便百度吧&#xff0c;实际场景中我们很多微服务的架构&#xff0c;都是有公共包&#xff0c;肯定是希望一个任务能够把公共包的配置加进去&#xff0c;一并构建&#xff0c;ok&#xff0c;直接上干货。 Jenkins 全局环境安装 pwd e…

建造者模式——复杂对象的组装与创建

1、简介 1.1、概述 建造者模式又称为生成器模式&#xff0c;它是一种较为复杂、使用频率也相对较低的创建型模式。建造者模式向客户端返回的不是一个简单的产品&#xff0c;而是一个由多个部件组成的复杂产品。 建造者模式是较为复杂的创建型模式&#xff0c;它将客户端与包…

【chatGpt】关于websocket连接中对未授权的捕捉问题

目录 问题 有效提问 有效的细节提问 问题 一路上&#xff0c;通过简单的error进行判断弹出授权&#xff0c;会有很多乱弹的现象&#xff1a; &#xff08;1&#xff09;链路正常切换会断 &#xff08;2&#xff09;服务器没有启动会连接不上 &#xff08;3&#xff09;没…

Pytorch深度学习-----DataLoader的用法

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

【Golang】基于OAuth2.0微信扫码实现客户端用户登录(原理+代码实现+视频讲解)

前言: 细心汇总,包括原理+配置+代码详细实现 文章目录 原理讲解什么是OAuth2.0解决方案授权码模式讲解认证流程Go语言实现微信扫码登录1. 内网穿透配置2. 微信测试账号申请3. 验证和微信服务器连接二维码生成回调地址测试原理讲解 什么是OAuth2.0 OAuth 2.0是一种授权协议,…

JavaScript学习 --消息摘要算法

消息摘要算法&#xff08;也称哈希算法&#xff09;是一种将任意大小的数据转换为一个固定大小的数据序列的算法。在JavaScript中&#xff0c;常见的消息摘要算法包括MD5、SHA-1、SHA-256等。它们适用于安全传输敏感数据、防篡改数据等场景。在本篇博客中&#xff0c;我们将介绍…

slurm/sbatch/srun 多步骤串行运行多个依赖性任务

在slurm系统下&#xff0c;有时候需要按步骤运行A、B、C三个任务&#xff0c;但是直接写在脚本里会同时提交&#xff0c;所以需要建立依赖关系。 错误做法&#xff1a; 搜索网上做法及slurm串行教程&#xff0c;做法多为如下&#xff0c;使用bash或python来按顺序/循环内来串…

顺序表详解

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&#xff0c;一起学习&#xff0c;一起进步&#…

NetApp FAS2750 和 FAS2820:适用于分布式企业和从远程到核心的 FAS

NetApp FAS2750 和 FAS2820&#xff1a;适用于分布式企业和从远程到核心的 FAS 拥有分布式企业和多个办公位置的客户希望使用这些系统进行虚拟化&#xff0c;以及为大型 FAS 和 AFF 系统提供简单且经济高效的备份和灾难恢复。 为什么要从 NetApp FAS 系列中选择一个型号&…

LLM / Python - json 使用详解

目录 一.引言 二.json 方法 1.json.dumps 2.json.dump 3.json.loads 4.json.load 三.json 参数 1.ensure_ascii 2.allow_nan 3.indent 4.sortKeys 5.Other 四.LLM 数据构建 1.json 数据构建 2.Train.py 五.总结 一.引言 上文中我们介绍了 LLama2-Chinese 的简…

ipad手写笔有必要买原装吗?质量好苹果平板平替笔推荐

因为iPad平板的强大&#xff0c;使得很多人群都用上了iPad&#xff0c;而且还在不断的普及。不管是用于绘画或者学习记笔记&#xff0c;都非常好用&#xff0c;但要是用来看电视剧玩游戏就没那么有价值了。如果你不打算购买昂贵的苹果电容笔&#xff0c;或者只是为了记录&#…

“数字中华 点亮未来”中华线上客户节 盛大开幕

2023年是中华保险数字化转型落地之年&#xff0c;峥嵘37载&#xff0c;中华保险在数字化转型上已经涌现了一批彰显辨识度、具有影响力的应用成果。7月15日&#xff0c;中华保险围绕数字化转型之路开展以“数字中华 点亮未来”为主题的37周年线上客户节活动&#xff0c;倾力打造…

直播平台源码开发提高直播质量的关键:视频编码和解码技术

在互联网日益发展的今天&#xff0c;直播平台成为人们互联网生活的主力军&#xff0c;直播平台功能的多样化与智能化使我们的生活有了极大地改变&#xff0c;比如短视频功能&#xff0c;它让我们既可以随时随地去发布自己所拍摄到的东西让世界各地的用户看到&#xff0c;也能让…

融合正余弦和折射反向学习的北方苍鹰优化算法,与金鹰/蜣螂/白鲸/霜冰算法对比...

今天的主角是&#xff1a;融合正余弦和折射反向学习的北方苍鹰优化算法(SCNGO)&#xff0c;算法由作者自行改进&#xff0c;目前应该没有文献这样做。 改进策略参照的上一期改进的麻雀优化算法&#xff0c;改进点如下&#xff1a; ①采用折射反向学习策略初始化北方苍鹰算法个体…

【字节跳动青训营】后端笔记整理-3 | Go语言工程实践之测试

**本文由博主本人整理自第六届字节跳动青训营&#xff08;后端组&#xff09;&#xff0c;首发于稀土掘金&#xff1a;&#x1f517;Go语言工程实践之测试 | 青训营 目录 一、概述 1、回归测试 2、集成测试 3、单元测试 二、单元测试 1、流程 2、规则 3、单元测试的例…

AQS抽象同步队列核心原理

CLH自旋锁 JUC中显式锁基于AQS抽象队列同步器&#xff0c;而AQS是CLH锁的一个变种。队列头结点可以获得锁&#xff0c;其他节点排队等候。 在争夺锁激烈的情况下&#xff0c;为了减少CAS空自旋&#xff08;CAS需要CPU进行内部通信保证缓存一致性造成流量过大引起总线风暴&…

【代码随想录day21】二叉搜索树中的众数

题目 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果树中有不止一个众数&#xff0c;可以按 任意顺序 返回。 假定 BST 满足如下定义&am…