【C++STL】模拟实现vector容器

news2024/11/26 14:38:38

文章目录

  • 前言
  • 一、vector的成员函数
  • 二、增删查改工作
    • 说明size()和capapcity()
    • 2.1reserve()
    • 2.2 resize()
    • 2.3 insert()
    • 2.4 erase()
    • 2.5 push_back()和pop_back()
  • 三、[]重载和迭代器
    • 3.1begin迭代器
    • 3.2 end迭代器
    • 3.3 []运算符重载
  • 四、默认成员函数
    • 4.1构造函数
    • 4.2 拷贝构造函数
    • 4.3 析构函数
  • 总结


前言

本文带你进入vector的模拟实现,对于vector,是我们深入学习STL的必要途径。


一、vector的成员函数

根据库的实现方式,成员函数如下:

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

c++11开始可以对成员变量使用缺省值,在这里我们可以使用缺省值。

二、增删查改工作

说明size()和capapcity()

在这里插入图片描述

size的大小为_finish - _start
capacity的大小为_end_of_storage - _start

2.1reserve()

该函数的作用是:扩容。

void reserve (size_type n);
  • 思路:
  • 1.如果要扩容的大小n小于capacity,则不会缩容。
  • 2.否则执行扩容。
    • 先申请一块n大小的空间
    • 拷贝原空间的数据到新空间,并释放原空间
    • 将新空间的数据交给_start管理。
//扩容
void reserve(size_t n)
{
	//防止有些情况是直接调用
	if (n > capacity())
	{
		size_t oldsize = size();//必须要记录旧的size,旧的size是用旧的_finish - _start获得的
		iterator tmp = new T[n];
		//memcpy(tmp, _start, sizeof(T) * size());
		//这里如果使用memcpy函数的话,如果vector对象是自定义类型,比如string,会造成浅拷贝问题。
		for (size_t i = 0; i < oldsize;i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		//_finish = _start + size();
		//如果这样用的话,size()是用旧的_finish - 新的_start,不是连续的空间,没有意义
		_finish = _start + oldsize;//_finish必须更新,旧的_finish迭代器已经失效了
		_end_of_storage = _start + n;
	}
	//tmp不用处理,出了作用域自动调用析构
}

注意:在扩容时,如果是自定义类型,比如string,使用memcpy函数进行拷贝数据,会出现浅拷贝问题。
在这里插入图片描述
v1会拷贝给v2,v2的指针仍然指向v1的string对象所指向的空间。这就造成了两次调用析构函数释放同一块空间的问题。
正确的解法是:
在拷贝时调用string的赋值运算符重载,完成深拷贝。

在这里插入图片描述

还有一个点是:

注意:在这里会出现迭代器失效的问题。

在这里插入图片描述

此时空间已满,则需要重新申请空间。

在这里插入图片描述

重新申请空间后,_start和_end_of_stortage都会指向新的空间,唯独_finish还指向旧的已经被释放的空间,此时如果这样更新_finish位置:

_finish = _start + size();

这里的size()是用旧的_finish - 新的_start得到,是没有意义的。

解决方案:
保存旧的size,随后_start加上旧的size即为_finish的位置。

2.2 resize()

该函数的功能是:重新改变vector容器的大小,让其到达n。新的空间默认初始化为val
思路:

  • 1.如果n小于size,则让_finish指向_start + n位置。
  • 2.如果n大于等于size,先调用reserve函数进行扩容,再将新的空间填充为val。
void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		//扩容到n,如果n<capacity(),则啥事不做,否则,扩容到n
		reserve(n);
		//将多的空间全部设置成缺省值
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

注意这里的缺省值为T(),是一个T类型的匿名对象。
不管T是什么,都可以使用模板实例化出具体的匿名对象,即使是内置类型,比如int,也可以实例化出int(),这里的值为0

2.3 insert()

在这里插入图片描述
在这里我们只实现最常用的接口。

该函数的作用是:

在pos位置插入模板对象val。

思路:

  • 插入元素前先检查容量
  • 如果容量满了,则调用reserve函数执行扩容操作
  • 然后从pos位置开始将其之后的元素全部都往后挪。
  • 再在pos位置插入val。
void insert(iterator pos, const T& val)
{
	assert(pos <= _finish);
	//插入前先检查扩容
	if (_finish == _end_of_storage)
	{
		size_t oldpos = pos - _start;//记录相对位置
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);

		pos = _start + oldpos;
	}

	//记录旧的size,防止出现迭代器失效问题。

	//将pos之后的位置往后挪,再插入
	iterator end = _finish - 1; // reserve之后,_finish也跟着更新了。
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	_finish += 1;
}

注意:insert在扩容时仍然会出现迭代器失效的问题。
解决方案:提前记录pos的相对位置,即oldpos = pos - _start;
然后再扩容完成后,更新pos 的位置,pos = _start + oldpos;
否则在扩容后,旧的pos迭代器就已经失效了。

2.4 erase()

该函数的功能是:删除pos位置的值。

//在删除最后一个位置或者只剩下一个待删除元素时,会出现迭代器失效问题。
iterator erase(iterator pos)
{
	assert(pos < _finish);
	iterator end = pos;
	while (end != _finish)
	{
		*end = *(end + 1);
		++end;
	}
	--_finish;

	return pos;
}

有一个需要注意的点:当我们删除元素后,pos迭代器就会失效,因为空间已经释放,这时候不能使用pos迭代器。
在这里插入图片描述

解决方法:返回删除位置的下一个元素的位置,即pos位置。
然而,在只剩下一个待删除元素或者删除最后一个位置的元素时,不能再使用pos迭代器。

2.5 push_back()和pop_back()

顾名思义,这两个函数的功能是在最后一个位置插入元素和删除最后一个位置的操作。
直接复用insert和erase函数即可,这里不再赘述。

三、[]重载和迭代器

typedef T* iterator;
typedef const T* const_iterator;

3.1begin迭代器

返回_start地址即可。

iterator begin()
{
	return _start;
}

const_iterator begin() const
{
	return _start;
}

3.2 end迭代器

返回_finish地址即可。

iterator end()
{
	return _finish;
}

const_iterator end() const
{
	return _finish;
}

3.3 []运算符重载

思路:只需给定pos下标即可。
在此之前需要判断pos位置不能超过整个顺序表的size大小。

T& operator[]( size_t pos)
{
	assert(pos < size());
	return *(_start + pos);
}

//不可修改的
const T& operator[]( size_t pos) const
{
	assert(pos < size());
	return *(_start + pos);
}

四、默认成员函数

4.1构造函数

构造函数可分为3种:

  • 无参构造
  • 构造n个val对象
  • 根据迭代区间构造

对于无参的构造,我们只需要初始化成员函数即可。

  • 1.无参构造
//无参构造函数
vector()
//:_start(nullptr)
//,_finish(nullptr)
//,_end_of_storage(nullptr)
{}
  • 2.构造n个val对象
//构造n个对象
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	//1.开空间
	reserve(n);
	//2.将空间填充为val对象
	for (size_t i = 0; i < capacity(); i++)
	{
		_start[i] = val;
	}
	//也可以复用resize
	//resize(n, val);
}

注意:这里在构造时必须初始化,如果不初始化,在调用reserve函数开空间时会出现访问野指针的问题。
也可以调用resize函数进行拷贝构造n个对象。

  • 3.使用迭代区间进行构造

同理,如果不初始化,会出现扩容后访问野指针的情况。

template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

4.2 拷贝构造函数

  • 1.拷贝构造传统写法
//拷贝构造,必须实现深拷贝
//传统写法
//v1(v)
vector(const vector<T>& v)
	//这里必须初始化,否则扩容时会访问不属于自己的空间
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
	//如果私有成员给了缺省值,就不用再再次初始化了
{
	reserve(v.capacity());
	//memcpy(_start, v._start, sizeof(T) * v.size());
	//这里使用memcpy的错误跟扩容的错误一样,不再赘述
	for (size_t i = 0; i < v.size(); i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
	{
		_start[i] = v._start[i];
	}

	_finish = _start + v.size();
}

拷贝构造需要注意的几个问题:

  • 1.拷贝构造同构造函数一样,如果不初始化,在开空间时会出现访问野指针的情况。
  • 2.拷贝构造必须进行深拷贝,否则如果vector的对象是自定义类型,比如string,会出现两次释放同一块空间的问题。

4.3 析构函数

释放_start指向的空间,然后全部置为空即可。

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

总结

vector的模拟实现就到此结束了,如果对你有帮助的话,不妨来一个关注吧!

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

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

相关文章

ahut 周赛3

A.gzm判试卷 AhutOj 线段树(注意,一定要开到4*N,不然会RE) 单点更新(求区间最值) 单点更新不需要懒标记,区间修改是大量的点,需要懒标记 AC代码: #include<iostream> #include<algorithm> #include<cstring> #include<cmath> using namespace st…

Jmeter接口测试从0到1打通,从安装到接口测试实例(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Jmeter简介 J…

安装adobe系列产品,提示错误代码81解决办法

安装adobe系列软件&#xff0c;如Photoshop、Premiere Pro、Illustrator等时&#xff0c;出现如下图提示错误代码81&#xff0c;如何解决呢&#xff1f;一起来看看。 解决方法一 (重启电脑等待5分钟再安装&#xff01;) 解决方法二 应用程序中打开Adobe Creative Cloud 点击…

分布式文件系统与HDFS的shell操作及查看元数据

启动hadoop和hive的metastore查看sbin的目录下的文件 执行./start-all.sh 查看相关的进程

node.js重装问题

目录 问题一&#xff1a; 问题二&#xff1a; node.js安装分享&#xff1a; node.js重装参考&#xff1a; 问题一&#xff1a; 使用&#xff08;npm install express -g&#xff09;报错&#xff1a; config global --global -local are deprecated. Use --locationglobal …

【软件测试】Window与Linux系统下-初始化Git环境(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Window 初始化 Gi…

python开发ESP32之环境配置(一)

1、开发编辑器 Thonny 2、串口驱动安装 通过type-c线将芯片链接电脑&#xff0c; 右击我的点电脑——管理——设备管理器&#xff0c;查看驱动安装是否正常。 异常情况下需要安装驱动&#xff08;CP2102USBQD),根据个人电脑配置选择相应的驱动程序 安装失败的话&…

AcWing1536. 均分纸牌 AcWing122. 糖果传递—数学推导、贪心

均分纸牌 && 糖果传递 均分纸牌糖果传递 均分纸牌 题目链接 AcWing1536. 均分纸牌 问题描述 分析 这道题有个特殊的地方就是A1只能从A2获取纸牌&#xff0c;或者A1只能将多余的纸牌给A2&#xff0c;此操作后A1的纸牌数应该为avg。 A2的纸牌只能从A3获取&#xff0c;…

PS VR2头显有望将与PC兼容,并实现破解6DOF跟踪功能

Sony在今年早些时候推出了专门为PlayStation 5打造的高级虚拟现实头显PS VR2&#xff0c;这款头显赢得了行业的赞誉&#xff0c;为用户提供了独特的体验。 然而&#xff0c;由于其高达599美元&#xff08;约合4283元人民币&#xff09;的售价&#xff08;相比于售价为399美元的…

MySQL学生表和分数表的多表查询

目录 一、创建学生表 二、创建分数表 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 4.从student表中查询计…

【Hello mysql】 mysql的索引

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的索引 mysql索引 索引索引是什么索引的分类索引作用查看 磁盘mysql的工作过程认识磁盘定位扇区磁盘随机访问(Random Access)与连续访问(Sequential Access)mysql和磁盘交互的基本单位 索引的理解建立测试表为何I…

Python(十四)数据类型——浮点型

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Redis(分布式缓存详解)

Redis 一、Redis简介1.1. 适用场景1.2. 常用数据类型1.3. 单点Redis缺陷 二、持久化机制&#xff08;解决数据丢失&#xff09;2.1. RDB2.1.1. RDB优缺点 2.2. AOF2.2.1. AOF配置2.2.2. AOF优缺点 三、Redis集群3.1. 主从&#xff08;解决并发读&#xff09;3.1.1. 主从数据同步…

HBase v2.2 高可用多节点搭建

最近刚刚完成了HBase相关的一个项目,作为项目的技术负责人,完成了大部分的项目部署,特性调研工作,以此系列文章作为上一阶段工作的总结. 前言 其实目前就大多数做应用的情况来讲,我们并不需要去自己搭建一套HBase的集群,现有的很多云厂商提供的服务已经极大的方便日常的应用使…

GEE:计算每个对象的面积、标准差、周长、宽度、高度

作者:CSDN @ _养乐多_ 本文记录了面对对对象分割,以及计算每个对象的面积、标准差、周长、宽度、高度的代码。 文章目录 一、代码一、代码 // 设置种子 var seeds = ee.Algorithms.Image.Segmentation.seedGrid(20)

node.js 第一天

目录 使用readFile()方法读取文件内容 判断文件是否读取成功 判断文件是否写入成功 案例 考试成绩整理 path path.join&#xff08;&#xff09; path.basename() 使用readFile()方法读取文件内容 // 1. 导入 fs 模块&#xff0c;来操作文件 const fs require(fs)// 2.…

Redis(二)网络协议和异步方式(乐观锁悲观锁)

Redis系列文章 Redis&#xff08;一&#xff09;原理及基本命令&#xff08;柔性数组&#xff09; Redis&#xff08;二&#xff09;网络协议和异步方式&#xff08;乐观锁&悲观锁&#xff09; Redis&#xff08;三&#xff09;存储原理与数据模型&#xff08;hash冲突、渐…

2023年NOC决赛-加码未来编程赛项决赛模拟题-Python模拟题--卷2

第一题: 题目:打印出所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个“水仙花数”,因为153=1的三次方+5的三次方+3的三次方。 第二题: 题目:企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10…

mysql invalid conn排查

mysql invalid conn排查 问题背景 服务使用golang &#xff0c;客户端库是go-mysql-driver ,系统测试环境频繁但是不总是报出invalid conn 错误&#xff0c;但实际拿sql执行时却是正常执行。 排查思路 原因分析 客户端使用了无效连接 由于连接无效&#xff0c;首先考虑客…

AI绘画StableDiffusion实操教程:冰霜旗袍美女

飞书原文链接&#xff0c;获取更多资源&#xff1a;AI绘画StableDiffusion实操教程&#xff1a;冰霜旗袍美女 前几天分享了StableDiffusion的入门到精通教程&#xff1a;AI绘画&#xff1a;Stable Diffusion 终极炼丹宝典&#xff1a;从入门到精通 但是还有人就问&#xff1a…