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

news2024/11/28 22:03:00

1.vector的介绍

  1. vector是表示可变大小数组的序列容器。
  2. vector采用的连续存储空间来存储元素。意味着也可以采用下标对vector的元素进行访问,和数组一样高效。但是它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  3. vector使用动态分配数组来存储它的元素,当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比时间需要的存储更大,不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  6. 与其他动态序列容器相比(deque、list and forward_list),vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其他不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

2.vector的基本结构

vector是一个顺序存储的容器,也可以说是一个数组,那么对于它的结构,我们可以用三个迭代器来组成,如下图:

在vector当中,其实迭代器就是一个指针。所以我们的vector类的基本成员变量可以这样设计。

template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start;
	iterator _finish;
	iterator _endofstorage;
};

3.vector模拟实现

3.1 构造函数

构造函数最主要的有两个:无参构造函数和拷贝构造函数。

3.1.1无参构造函数

无参构造函数只需要将全部迭代器赋值空。 

//无参构造函数:直接全部给nullptr
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

3.1.2 带参数的构造函数(包括size_t和int类型)

对于带参数的构造函数,当参数类型为size_t时,会先为vector分配指定数量的空间,并为每个元素进行初始化。

vector(size_t n, const T& val = T())
{
	_start = new T[n];
	_finish = _start;
	_end_of_storage = _start + n;
	for (size_t i = 0; i < n; i++)
	{
		*_finish++ = val;
	}
}

3.1.2区间构造函数

区间构造函数接收两个迭代器first和last,由于不同类型的容器迭代器类型可能不同,因此设计成函数模板,将区间内的内容尾插到vector,但是调用push_back接口通过_finish == _endofstorage判断是否满,需要初始化

template<class InoutIterator>
vector(InoutIterator first, InoutIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

3.1.3 拷贝构造函数

拷贝构造的模拟实现可以通过一个个尾插的方式实现:

vector<int> v2(v1);

即将v1的数据从头到尾遍历一遍,遍历的时候顺便将v1的数据尾插到v2。

 

//拷贝构造函数
//vector<int> v1;
//vector<int> v2(v1);
vecotr(const vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{
	const_iterator it = v.begin();//定义一个迭代器指向v1的头
	while (it != v.end())
	{
		push_back(*it); //将v1的数据一个个尾插到v2中,这里尾插在后面实现
		it++;
	}
}

注意:不要使用memcpy函数拷贝数据,如果数据是内置类型或浅拷贝的自定义类型,使用memcoy是没什么问题的,但如果数据是需要深拷贝的自定义类型(string),就会出现问题,拷贝的数据和源数据指向同一块空间,因此,我们使用for循环依次赋值,调用string的赋值运算符重载完成深拷贝,push_back在实现的时候会调用深拷贝类型的赋值运算符重载。

 3.2 析构函数

将空间释放掉,_start,_finish,_endofstorage置为空指针

//析构函数
~vector()
{
    delete[] _start;
    _start = _finish = _endofstrorage = nullptr;
}

3.3赋值运算符重载

3.3.1传统写法

vector<T>& operator=(const vector<T>& v)
{
	//防止自己给自己赋值
	if (this != &v)
	{
		//1.开辟空间
		T* tmp = new T[v.capacity()];
		//2.将数据拷贝到新空间当中
		for (int i = 0; i < v.size(); i++)
		{
			tmp[i] = v._start[i];
		}
		//3.释放原有空间
		delete[] _start;
		//4.指向新空间
		_start = tmp;
		_finish = _start + v.size();
		_endofstorage = _start + v.capacity();
	}
	return *this;
}

3.3.2现代写法

//赋值运算符重载现代写法
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}
//只需要交换this和v的桑指针即可
void swap(vector<T>& v)
{
	//调用标准库中的swap函数
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
	
}

注意:传值传参时,自定义类型会调用拷贝构造函数形成形参,形参是实参的一份临时拷贝,因此我们可以让这个形参跟我们的this交换。

这样我们的this就成功被复制为我们想要的值了,this指向的旧空间在交换后被形参v所指向,出了这个作用域之后,形参v会调用其析构函数释放掉this指向的旧空间

因此,只需要传值传参swap交换就可以完成开辟新空间+拷贝数据+释放原有空间+指向新空间

3.4扩容函数 reserve

当n大于对象当前的capacity时,将capacity扩大到n或大于n

当n小于对象当前的capacity是,就不需要

实现步骤:

1.新开辟一块空间,若容器为空,将_start,_finish指向新开辟空间的首元素地址,_endofstorage指向新开辟空间的最后一个元素下一个位置

2.若容器不为空,将数据拷贝到新空间,释放掉旧空间,更新_start,_finish,_endofstorage的位置

注意:将数据拷贝到新空间,仍然不能用memcpy函数,因为对于需要深拷贝的自定义类型,使用memcpy函数以后,新开辟空间里的元素原空间里的元素所指向的内存空间是一样的,当旧空间被释放时,会调用自定义类型的析构函数,从而使得新开辟空间里的元素指向的内存空间也被释放 

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];//新开辟一块空间
		//容器不为空
		if (_start)
		{
			size_t sizec = size();// 计算原来容器size个数
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;//释放旧空间
			_start = tmp; //更新_start
			_finish = _start + sizec;// 更新_finish
			_endofstorage = _start + n; // 更新_endofstorage
		}
		//容器为空,更新_start,_finish,_endofstorage的位置
		else
		{
			_start = _finish = tmp;
			_endofstorage = _start + n;
		}
	}
}

 3.5改变数组长度函数 resize

当n<size时,直接将_finish = _start+n(将有效数据长度缩小)

当size<n<=capacity时,我们将有效数据的长度增加到n,增加出来的有效数据内容是val

当n>capacity时,先调用上面的reserve函数进行增容,再将有效数据的长度增加到你,增加出来的有效数据内容是val

void resize(size_t n, const T& val = T())
{
	//n<size()
	if (n < size())
	{
		_finish = _start + n;
	}
	//n>size()
	else
	{
		//增容
		if (n > capacity())
			reserve(n);
		//填充数据val
		size_t count = n - size();
		while (count--)
		{
			*_finish = val;
			++_finish;
		}
	}
}

 3.6 判空函数 empty

判断size() == 0

bool empty() const
{
    return size() == 0;
}

3.7 尾插函数 push_back

尾插入数据,首先要检查是否已满,已满则进行增容,增容后尾插

void push_back(const T& x)
	{
		//扩容
		if (_finish == _end_of_storage)
		{
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			
		}
		
		*_finish = x;
		++_finish;
	}

3.8 尾删函数 pop_back

对于尾删,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish--

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

3.9 插入函数 insert

容量不够,先增容,增容之前先记录下pos - _start的值,否则增容之后,pos还指向原来已经被释放的空间;

将pos位置往后的数据往后挪动一位,在pos位置插入值val;

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

		//扩容
		if (_finish == _end_of_storage)
		{
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			pos = _start + len;
		}
		iterator end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			--end;
		}
		*pos = x;
		++_finish;
		return pos;
	}

 3.10 删除函数 erase

容器若为空,则做断言处理,若不为空,将pos位置往后的数据向前挪动一位,删除数字之后返回pos位置的迭代器,否则会失去该位置的迭代器,导致失效。

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

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

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

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

相关文章

三子棋(C 语言)

目录 一、游戏设计的整体思路二、各个步骤的代码实现1. 菜单及循环选择的实现2. 棋盘的初始化和显示3. 轮流下棋及结果判断实现4. 结果判断实现 三、所有代码四、总结 一、游戏设计的整体思路 &#xff08;1&#xff09;提供一个菜单让玩家选择人机对战、玩家对战或者退出游戏…

企业电子印章主要通过以下几种方式进行防伪

企业电子印章主要通过以下几种方式进行防伪&#xff1a; 一、数字证书和加密技术 数字证书认证 企业电子印章依托数字证书&#xff0c;数字证书由权威的第三方数字认证机构颁发&#xff0c;确保了印章使用者的身份真实性。 数字证书如同企业在数字世界的身份证&#xff0c;包…

Python 工具库每日推荐 【sqlparse】

文章目录 引言SQL解析工具的重要性今日推荐:sqlparse工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:SQL查询分析器案例分析高级特性自定义格式化处理多个语句扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 Python工具库每日推荐 专…

SpringCloud-持久层框架MyBatis Plus的使用与原理详解

在现代微服务架构中&#xff0c;SpringCloud 是一个非常流行的解决方案。而在数据库操作层面&#xff0c;MyBatis Plus 作为 MyBatis 的增强工具&#xff0c;能够简化开发&#xff0c;提升效率&#xff0c;特别是在开发企业级应用和分布式系统时尤为有用。本文将详细介绍 MyBat…

我们是不是有点神话了OPENAI和CHATGPT?OPENAI真的Open?

网上很多人大力推荐和神化OPENAI的CHATGPT等产品&#xff0c;好像这神器无所不能!也不知道是VPN代理商为了给自己做广告&#xff1f;还是CHATGPT注册代理推销产品?或者有可能是国外宣传CHATGPT文章直接翻译过来的?不可否认CHATGPT确实是一款伟大的产品&#xff0c;但有些情况…

HarmonyOS的DevEcoStudio安装以及初步认识

目录 1.DevEco下载 2.DevEco安装 3. 未开启Hyper-V 1--开启Hyper-v流程 4.编译错误 5.目录结构 1&#xff09;AppScope 2&#xff09;entry: 3&#xff09;build 4&#xff09;entry->src 5&#xff09;entry->src->main->etc 6&#xff09;entry->src->main…

Shell编程-if和else

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们前面学习了那么多命令&#xff0c;以及涉及到部分逻辑判断的问题。从简单来说&#xff0c;他就是Shell编程&#xff0c;…

一键快捷回复软件助力客服高效沟通

双十一临近&#xff0c;电商大战一触即发&#xff01;在这个购物狂欢的热潮中&#xff0c;客服团队的效率至关重要。今天我要和大家分享一个非常实用的快捷回复软件&#xff0c;特别是为电商客服小伙伴们准备的。这款软件能够极大地提高你的工作效率&#xff0c;让你在处理客户…

小程序开发设计-模板与配置:WXML模板语法⑨

上一篇文章导航&#xff1a; 小程序开发设计-协同工作和发布&#xff1a;协同工作⑧-CSDN博客https://blog.csdn.net/qq_60872637/article/details/142455703?spm1001.2014.3001.5501 注&#xff1a;不同版本选项有所不同&#xff0c;并无大碍。 目录 上一篇文章导航&…

OpenAI 公布了其新 o1 模型家族的元提示(meta-prompt)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

开发板资源介绍【STM32MP157 学习笔记】

引言 FS-MP1A 开发板是基于 ST&#xff08;意法半导体&#xff09;公司的 STM32MP1 系列微处理器设计的先进开发板。该系列处理器集成了 Arm Cortex-A7 和 Cortex-M4 两种内核的异构架构&#xff0c;在高性能和灵活性的基础上&#xff0c;保证了低功耗、实时控制和高度集成化。…

阿里 C++面试,算法题没做出来,,,

我本人是非科班学 C 后端和嵌入式的。在我面试的过程中&#xff0c;竟然得到了阿里​ C 研发工程师的面试机会。因为&#xff0c;阿里主要是用 Java 比较多&#xff0c;C 的岗位比较少​&#xff0c;所以感觉这个机会还是挺难得的。 阿里 C 研发工程师面试考了我一道类似于快速…

深度学习-22-基于keras的十大经典算法之深度神经网络DNN

文章目录 1 深度神经网络(DNN)1.1 DNN简介1.2 DNN基本结构2 模拟应用2.1 构建模型2.2 训练模型2.3 模型预测3 鸢尾花数据集3.1 加载数据3.2 构建模型3.3 训练模型3.4 模型预测4 问题及解决5 参考附录1 深度神经网络(DNN) 1.1 DNN简介 背景:深度神经网络(DNN)也叫多层感…

歌曲怎么去掉原唱只留伴奏?创作无界,轻松获取伴奏音轨

在音乐制作、翻唱或是卡拉OK等场合&#xff0c;我们经常需要歌曲的伴奏版本&#xff0c;即去掉原唱声音&#xff0c;只保留背景音乐的部分。然而&#xff0c;并非每首歌曲都会官方发布伴奏版本&#xff0c;这时我们就需要借助一些技术手段来实现这一目标。本文将介绍几种常见的…

linux线程 | 同步与互斥(上)

前言&#xff1a;本节内容主要是线程的同步与互斥。 本篇文章的主要内容都在讲解互斥的相关以及周边的知识。大体的讲解思路是通过数据不一致问题引出锁。 然后谈锁的使用以及申请锁释放锁的原子性问题。 那么&#xff0c; 废话不多说&#xff0c; 现在开始我们的学习吧&#x…

使用OpenCV实现基于EigenFaces的人脸识别

引言 人脸识别技术近年来得到了飞速的发展&#xff0c;它被广泛应用于安全监控、门禁系统、智能设备等领域。其中&#xff0c;基于特征脸&#xff08;EigenFaces&#xff09;的方法是最早期且较为经典的人脸识别算法之一。本文将介绍如何使用Python和OpenCV库实现一个简单的人…

【LeetCode】每日一题 2024_10_15 三角形的最大高度(枚举、模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;三角形的最大高度 代码与解题思路 久违的简单题 这道题读完题目其实不难想到有两条路可以走&#xff1a; 1、题目很明显只有两种情况&#xff0c;枚举是第一个球是红球还是蓝球这两种情…

导数的概念及在模型算法中的应用

一. 导数概念与计算 1. 导数的物理意义&#xff1a; 瞬时速率。一般的&#xff0c;函数yf(x)在x处的瞬时变化率是 2. 导数的几何意义&#xff1a; 曲线的切线&#xff0c;当点趋近于P时&#xff0c;直线 PT 与曲线相切。容易知道&#xff0c;割线的斜率是当点趋近于 P 时&…

UE5学习笔记25-游戏中时间同步

一、原因 1.由于网络问题会导致服务器上的时间和客户端上获得的时间不一致 二、解决方法 在程序启动时向服务器请求服务器的时间返回给客户端并获得客户端发送消息的往返的时间&#xff0c;在获得客户端上的时间&#xff0c;用服务器上获得的时间加上往返时间减去客户端上的时…

稳字诀! 洞见 强者的社交格局:从不恋战——早读(逆天打工人爬取热门微信文章解读)

都是文字 引言Python 代码第一篇 洞见 强者的社交格局&#xff1a;从不恋战第二篇 稳字诀结尾 引言 今天很奇怪 一直都挺烦造的 好像有很多事情忙 但是就是忙着找不定 不能定下心来 主要还是在股市 其他方面应该没啥 计划表还是不够给力 没办法把心在约定住 稳字诀 勤燃香,奋…