【C++】STL——vecot的模拟实现

news2024/11/13 10:22:00

目录

  • 前言
  • 总体结构
  • 默认成员函数
    • 构造函数
    • 拷贝构造
    • 赋值重载
    • 析构函数
  • vector的相关容量空间以及访问的实现
    • capacity()和size()
    • 迭代器实现
    • operator[]
    • reserve
  • vector类对象的修改操作
    • 尾插尾删
    • 任意位置插入
    • 任意位置删除
    • 交换和清理

请添加图片描述

前言

  前面我们已经学习了解了vector重要接口的使用:【C++】vector的使用。
  下面我们继续来模拟实现vector,这对学习STL有重大帮助。

总体结构

  同样,vetor在标准库中已经实现好了,因此在模拟实现时我们可以使用命名空间进行隔离,防止命名冲突
  在vector的使用中我们就已经介绍过源码中的vector是如何写的:

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

  和在c语言中的动态顺序表不太一样,vector是由三个指针变量来实现的
在这里插入图片描述

默认成员函数

构造函数

在这里插入图片描述
对照着库里面的构造函数可以模拟实现如下:

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

//构造
//函数模板——支持任意迭代器的迭代区间进行初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}
vector(int n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

注意:
1.当所写函数中没有默认构造时,我们就可以写上一句:

vector() = default;

即可强制编译器生成默认构造,当然直接自己写vector(){}也可以。


2.vector(size_t n, const T& val = T())它的缺省值给的是T(),即匿名对象
我们知道,对匿名对象如果是自定义类型,就调用它的默认构造。如果是内置类型C++对内置类型进行了升级,让其也有默认构造
如:

int k = int();//也是正确的

3.我们还要再写一个vector(int n, const T& val = T())是因为如果不写时,我们写vector< int > v(3,5)时会优先匹配上迭代器构造,此时InputIterator会被替代成为int,而解引用就会报错:

error C2100: 非法的间接寻址

拷贝构造

  在进行拷贝构造前先扩容,可以避免后期频繁扩容会存在过多的内存碎片。

//拷贝构造v3(v2)
vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (auto e : v)
	{
		push_back(e);
	}
	//两种写法都可以
	/*for (size_t i = 0; i < v.size(); i++)
	{
		push_back(v[i]);
	}*/
}

  逐个数据赋值拷贝,属于深拷贝,可以避免浅拷贝的问题。

赋值重载

//传统写法
/*vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		reserve(v.capacity());
		for (size_t i = 0; i < v.size(); i++)
		{
			_start[i] = v[i];
		}
		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
	}
	return *this;
}*/

//现代写法,都交给编译器去完成
//v1=v3
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

  对于现代写法,我们使用传值传参对于自定义类型,编译器会调用它的拷贝构造,即v是v3的一个拷贝构造,直接交换,可使得v1的值换给v,v是一个形参,出了作用域会调用析构函数销毁

析构函数

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

  我们前面在构造开空间时用的是new[],因此在析构时也要使用delete[]

vector的相关容量空间以及访问的实现

capacity()和size()

  对于vector的一系列增删查改都离不开对空间的判断,看容量是否充足,若不充足则需要扩容,即reserve。
  对于vector而言,获取数据个数size()和容量大小capacity()都还是比较简单的:

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

  vector的三个成员变量都是指针,因此我们直接采用指针-指针的方式即可得到相应的数据。

迭代器实现

  同样,迭代器也是比较简单的,我们可以将其认定为T*的一个原生指针即可,同样还有const迭代器,即const对象对应的只能访问不能修改。

typedef T* iterator;
typedef const T* const_iterator;

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

operator[]

迭代器主要实现访问的功能,那operator[]也不能少,也比较简单:

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

reserve

  当我们想要增加数据时,我们就要判断我们的容量空间是否足够,如果不足就需要先扩容再插入数据,当然这涉及到深浅拷贝的问题:

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];//开辟新空间
		size_t oldsize = size();//不能省略
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * oldsize);//浅拷贝不能使用
			for(size_t i = 0; i < oldsize; i++)
			{
				tmp[i] = _start[i];//深拷贝
			}
			delete[] _start;//释放旧空间
		}
		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

注意:为什么说对oldsize的定义不能省略
是因为在size()的函数定义中,它是由_start和_finish两个指针相减而来。而后面_start已经到了新空间,而_finish还是属于旧空间上的指针,二者相减就会报错。

在这里插入图片描述
  通过调试可以发现,对于memcpy是浅拷贝会使得tmp和_start的指针同时指向同一块空间,析构则会析构两次而崩溃,因此还是要逐个数据赋值拷贝。

vector类对象的修改操作

尾插尾删

void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = x;
	_finish++;
}
void pop_back()
{
	assert(size() > 0);
	--_finish;
}

  在尾插时要注意判断空间是否足够,不够还要记得扩容。
  尾删时也要判断size是否大于0,避免删除过多导致崩溃。

任意位置插入

iterator insert(iterator pos, const T& x)
{
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;//不能省略
		iterator newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
		pos = _start + len;//更新pos,避免变成野指针
	}
	iterator end = _finish - 1;
	while (pos <= end)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	_finish++;
	return pos;
}

对于insert可以认为是以下几个步骤:

  1. 判断数据是否需要扩容
  2. 往后挪动数据
  3. 插入数据

  而对于size_t len = pos - _start;pos = _start + len;这两行代码是不能省略的,因为牵扯到扩容,不更新pos就会导致迭代器失效的问题。

任意位置删除

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);
	iterator end = _finish - 1;
	while (pos < end)
	{
		*pos = *(pos + 1);
		pos++;
	}
	--_finish;
	return pos;
}

  任意位置的删除只需要将该位置后面的数据挪动覆盖即可,同样存在着迭代器pos失效的问题,这在上一节就已经说了,因此我们要有返回值返回被删除元素的下一个元素即可

交换和清理

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}
void clear()
{
    erase(begin(), end());
}

  库里面其实已经提供了std::swap函数,但vector还是有自己的swap函数,是因为库里面的交换函数涉及到拷贝较多,效率较低,而vector自己的交换函数只需要交换指针即可,没有拷贝,效率高


感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
请添加图片描述

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

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

相关文章

Appium如何简化混合App的测试和自动化

背景&#xff1a;Hybrid App&#xff08;混合模式移动应用&#xff09;是指介于web-app、native-app这两者之间的app&#xff0c;兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。 什么是混合型应用&#xff08;Hybrid App&#xff09; 我们可以理解…

iPhone 16预售已开,沙漠金色最抢手,喜提新机后别忘了这件事!

9月13日20点&#xff0c;iPhone 16系列正式开启官方预购。今年全新的iphone16不仅新增相机按钮和AI功能&#xff0c;还增加了沙漠金配色。“加量不加价”的iPhone 16系列开售依旧火爆&#xff0c;iPhone 16系列开售1分钟内&#xff0c;苹果官方网站一度被消费者买到崩&#xff…

EasyExcel 快速入门

目录 一、 EasyExcel简介 官网链接&#xff1a; 代码链接&#xff1a; 二、 EasyExcel快速上手 引入依赖&#xff1a; 设置Excel相关注解 编写对应的监听类&#xff1a; 简单写入数据&#xff1a; 简单读取数据&#xff1a; 不需要使用监听器&#xff1a; 需要使…

U盘一打开就让格式化怎么办?教你快速解决方法

在日常生活和工作中&#xff0c;U盘已成为我们存储和传输数据的重要工具。然而&#xff0c;有时我们会遇到一个令人头疼的问题&#xff1a;当插入U盘后&#xff0c;电脑提示需要格式化才能使用。这时&#xff0c;我们该如何应对呢?本文将为大家详细介绍U盘提示格式化的原因及解…

野生动物检测系统源码分享

野生动物检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

时序必读论文06|PITS : 基于非依赖策略学习时序patch特征表示

论文标题&#xff1a;LEARNING TO EMBED TIME SERIES PATCHES INDEPENDENTLY 下载地址&#xff1a;https://arxiv.org/pdf/2312.16427v1.pdf 开源代码&#xff1a;https://github.com/seunghan96/pits 前言 之前的文章我们读了Patch TST&#xff0c;建议大家阅读原论文&…

数据结构修炼——顺序表和链表的OJ题练习

目录 一、顺序表相关OJ题1 移除元素题目解析 2 合并两个有序数组题目解析 二、链表相关OJ题1 移除链表元素题目解析 2 反转链表题目解析 3 链表的中间结点题目解析 4 合并两个有序链表题目解析 5 链表的回文结构题目解析 6 相交链表题目解析 7 环形链表的判断题目解析 8 环形链…

共享单车轨迹数据分析:以厦门市共享单车数据为例(四)

副标题&#xff1a;共享单车与地铁接驳距离探究——以厦门市为例 关于轨道交通站点接驳范围的研究早已屡见不鲜&#xff0c;通常认为以站点为圆心、800米作为地铁站直接的服务范围是合理的。近年来&#xff0c;随着轨道、公交和慢行交通三网融合概念的提出&#xff0c;慢行交通…

9.第二阶段x86游戏实战2-初识易语言

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

喜报 | 大势智慧荣获国家级专精特新重点“小巨人”企业荣誉称号

近日&#xff0c;湖北省经济和信息化厅发布了《关于2024年拟支持专精特新重点“小巨人”企业名单的公示》。武汉大势智慧科技有限公司&#xff08;后简称“大势智慧”&#xff09;凭借在实景三维AI领域的卓越技术表现和创新能力&#xff0c;成功上榜并通过公示期&#xff0c;荣…

预算不多怎么选一款开放式耳机?四款亲测好用的蓝牙耳机推荐

挑选开放式耳机可以从以下几个方面入手&#xff1a; 关注佩戴舒适度&#xff1a; 外观设计&#xff1a;开放式耳机有耳挂式、夹耳式等多种设计。耳挂式耳机通常佩戴较为稳固&#xff0c;适合运动时使用&#xff1b;夹耳式耳机相对轻便&#xff0c;但可能需要一定时间适应其佩…

多核DSP(6000系列)设计与调试技巧培训

​课程介绍&#xff1a; 为帮助从事DSP开发工程师尽快将DSP技术转化为产品&#xff0c;在较短时间内掌握DSP设计技术和问题的解决方法&#xff0c;缩短产品开发周期、增强产品竞争力、节省研发经费。我们特组织了工程实践和教学经验丰富的专家连续举办了多期DSP C6000的培训&a…

六氟化硫密度微水在线监测配套5孔M12格兰头航空插头插座

我们将为大家介绍如何使用六氟化硫密度微水在线监测配套5孔M12格兰头连接器。在本教程中&#xff0c;我们将向您展示简单易懂的步骤&#xff0c;让您轻松掌握。 所需材料&#xff1a; 1. 六氟化硫密度微水在线监测器 2. 5孔M12格兰头连接器 3. 电源线 4. 符合要求的电缆 5…

批量视频压缩需要怎么压缩?2024帮助你快速进行视频压缩的软件

批量视频压缩需要怎么压缩&#xff1f;2024帮助你快速进行视频压缩的软件 批量视频压缩是处理大量视频文件时常见的需求&#xff0c;特别是当你需要减小视频大小以便存储或上传时。以下是5款帮助你快速进行视频压缩的软件&#xff0c;它们操作简单&#xff0c;功能强大&#x…

三、k8s中的控制器的使用

一 什么是控制器 官方文档&#xff1a; 工作负载管理 | Kubernetes 控制器也是管理pod的一种手段 自主式pod&#xff1a;pod退出或意外关闭后不会被重新创建 控制器管理的 Pod&#xff1a;在控制器的生命周期里&#xff0c;始终要维持 Pod 的副本数目 Pod控制器是管理pod…

Java高级Day41-反射入门

115.反射 反射机制 1.根据配置文件re.properties指定信息&#xff0c;创建Cat对象并调用hi方法 SuppressWarnings({"all"}) public class ReflectionQuestion {public static void main(String[] args) throws IOException {//根据配置文件 re.properties 指定信息…

最新热点!结合创新!小样本学习+CLIP:超好上手的思路,爽发顶会顶刊

今天给大家推荐一个很好上手的创新思路&#xff1a;小样本学习CLIP。 这个思路的优势在于&#xff1a;通过利用CLIP模型强大的跨模态表征能力&#xff0c;再结合小样本学习技术&#xff0c;我们就可以在仅提供少量标注样本的情况下&#xff0c;快速适应新的任务&#xff0c;在…

elementui组件el-upload实现批量文件上传

el-upload组件上传文件时&#xff0c;每传一个文件会调一次接口&#xff0c;所以当上传多个文件的时候&#xff0c;有 n 个文件就要调 n 次接口。 刚好之前工作中遇到使用el-upload组件批量上传文件的需求&#xff0c;来看看怎么实现。 思路&#xff1a; 1.取消组件的自动上…

【C++】vector常见用法

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;C从小白到高手 &#x1f339;往期回顾&#x1f339;&#xff1a;[C]string类 &#x1f516; 流水不争&#xff0c;争的是滔滔不息。 文章目录 一、vector的介绍vector…

KubeCon China 回顾|快手的 100% 资源利用率提升:从裸机迁移大规模 Redis 到 Kubernetes

大家下午好&#xff0c;我是来自 ApeCloud 的吴学强&#xff0c;非常高兴能够在 KubeCon 做分享。今天的分享由我和来自快手的刘裕惺同学共同完成&#xff0c;我们分享的主题是将大规模的 Redis 实例从裸机迁移到 Kubernetes 上来提高资源的利用率。 我们今天的议题包括几个方…