探索C++中的动态数组:实现自己的Vector容器

news2025/1/16 3:34:21

请添加图片描述

🎉个人名片

🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉
————————————————

🎉文章简介

🎉本篇文章将 介绍如何使用C++编写代码来实现一个类似于STL中的Vector容器 等学习的相关知识进行分享!

💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉
——————————————————————————

一.前言

这篇文章将介绍如何使用C++编写代码来实现一个类似于STL中的Vector容器。Vector是一种动态数组,它可以根据需要自动调整大小,并提供了许多方便的方法来操作数据。在这篇文章中,你将学习如何使用指针和动态内存分配来创建一个可变大小的数组,并实现Vector的常见功能,如添加元素、删除元素、访问元素等。通过实现自己的Vector容器,你将更好地理解动态数组的原理和实现方式,并提升对C++语言的理解和掌握。

二.Vs下Vector的底层结构

vs下底层是一个类,类里面的成员变量包括三个指针,指针类型为所存储数据类型(T)的指针;
T* _start 指向的是存储数据所开空间的起始位置;
T* _finish 指向的是最后一个数据的下一个位置;
T* _endofstorage 指向的是所开空间的最后的下一个位置;

如图:
在这里插入图片描述

public:
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start;
	iterator _finish;
	iterator _endofstorage;

三.vector的模拟实现

1.构造函数

1.直接初始化为空指针,使用时再开空间

vector()
	:_start(nullptr)
	,_finish(nullptr)         //也可以在定义的时候直接给缺省值
	,_endofstorage(nullptr)
{}

2.用一个迭代器区间构造(需要复用下面实现的push_back函数)

	template<class intputiterator>     //模板
	vector(intputiterator first, intputiterator last)
	{
		while (first != last)  //不能是用<判断,因为底层不一定连续
		{
			push_back(*first);    //依次取里面的数据尾插
			++first;
		}
	}

3.用n个T类型构造对象(这里需要后面实现的resize函数)

vector(size_t n,const T& x = T())
{
	resize(n,x);
}
vector(int n, const T& x = T())
{
	resize(n,x);
}

注意:这里为什么要实现两个?
在这里插入图片描述

2.reserve函数

结合下面代码和图解看看思路解析:
1.reserve函数可以单独使用,也可以在其他接口种会使用,我们实现的该函数不会缩容,所以最开始会加一个判断是否缩容;
2.reserve函数的实现是开一个新空间,将原空间的数据拷贝到新空间,再对_start,_finish,_endofstorage 进行处理;
3.这里需要记录一个原空间存储的有效数据的个数,为了确定_finish的位置(如果不存储,则当开辟好了新空间后,_finish的位置不能确定)
图解:
在这里插入图片描述

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();      //旧空间有效数据个数
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();   //需要判断,因为可能为0
		iterator tmp = new T[newcapacity];     //开空间
		//memcpy(tmp, _start,old_size * sizeof(T));   //下面会将为什么不用memmove函数
		for (int i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];       //拷贝数据
		}
		delete[] _start;             //释放旧空间
		_start = tmp;
		_finish = _start + old_size;      //确定_finish的位置
		_endofstorage = _start + newcapacity;     
	}
}

为什么不用memmove?
当我们存储的数据类型为string时【如图一】每个string对象里面都有一个_str,指向一个字符串,当我们用memmove拷贝后,拷贝后的_str与拷贝前的_str指向同一块空间【如图二】,当我们释放_start的时候,会调用string的析构函数,将该空间释放掉,就会导致野指针问题;

在这里插入图片描述

3.push_back函数

思路:检查是否空间满了,扩容,直接尾插

	void push_back(const T& x)
	{
		if (_finish == _endofstorage)    //检查是否需要扩容
		{
			reserve(capacity() == 0 ? 4 : 2 * capacity());
		}

		*_finish = x;     //尾插

		++_finish;    //更新下标
	}

4.push_back函数

思路:与顺序表实现一样,直接–_finish;

void pop_back()
{
	assert(size());   //检查是否还有有效数据可删
	--_finish;
}

5.resize函数

思路:
分为三种可能:
1.n>capacity 扩容+尾插
2.size<n<capacity 直接尾插
3.n<size 尾删

值得注意的是传参给了缺省值,因为存储数据可能是string这种数据,给了缺省值会去掉对应的默认构造函数,虽然内置类型没有默认构造,但是为了解决这类问题,有了类似于默认构造类处理内置类型;


		void resize(size_t n,T x=T() )   
		{
			if (n<size())     //直接改变_finish
			{
				_finish = _start + n;;
			}
			else
			{
				if (n > capacity())   //扩容
				{
					reserve(n);
				}
				for (size_t i = size(); i <= n; i++)  //尾插
				{
					_start[i] = x;      //可以复用push_back函数
				}
				_finish = _start + n;    //更新
			}
			
		}

6.insert函数

思路:
1.检查是否需要扩容
2.挪动数据
3.插入,更新下标

iterator insert(iterator pos, const T& x)
{
	assert(pos);
	assert(pos >= _start);     //断言
	assert(pos <= _finish);
	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;              //记录之前的值
		reserve(capacity() == 0 ? 4 : 2 * capacity();    //扩容
		pos = _start + len;          //更新pos下标
	}
	//memmove(pos+1, pos, sizeof(T) * (_finish - pos));  
	iterator end = _finish-1;
	while (end>=pos)
	{
		*(end+1) = *end;     //拷贝
		end--;
	}
	*pos = x;     //插入
	++_finish;    

	return pos;    //返回pos位置的迭代器,防止迭代器失效问题
}

7.erase函数

直接挪动数据覆盖

	iterator erase(iterator pos)
	{
		assert(pos < _finish && pos >= _start);   //断言

		iterator end = pos;
		while (end < _finish)
		{
			*end = *(end + 1);   //挪动数据覆盖
			end++;
		}
		--_finish;          //更新下标

		return pos;      //返回pos位置的迭代器,防止迭代器失效问题
	}

8.swap函数


		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage,v._endofstorage);
		}

9.赋值运算符重载

与上章《魔法之线:探索string类的神秘世界》链接: link赋值运算符重载方法一样;

现代写法:

	vector<T>& operator=(vector<T> v)
	{
		swap(v);      

		return *this;
	}

10.拷贝构造函数

传统写法:

	vector(const vector<T>& v)
	{
		iterator tmp = new T[v.capacity()];
		memcpy(tmp, v._start, sizeof(T) * v.size());
		_start = tmp;
		_finish = _start + v.size();
		_endofstorage = _start + v.capacity();

	}

稍便捷的方式
直接复用尾插函数,将对象v的数据一个一个尾插;

		vector(vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			reserve(v.capacity());      //提前空间,减少尾插扩容
			for (const auto& e : v)
			{
				push_back(e);
			}
		}

11.其他简单函数的实现:

		//判空
		bool empty()
		{
			return size();
		}
		//返回第一个数据
		T& front()const
		{
			return *_start;
		}
		//返回最后一个数据
		T& back()const
		{
			return *(_finish-1);
		}
		//返回有效数据个数
		size_t size()const
		{
			return _finish - _start;
		}
		//返回容量
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		//[]运算符重载
		T& operator[](size_t pos)
		{
			assert(pos>=0 && pos<size());

			return _start[pos];
		}

		T& operator[](size_t pos)const
		{
			assert(pos >= 0 && pos < size());

			return _start[pos];
		}
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

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

💕最后希望内容对大家有所帮助😊😊😊

请添加图片描述

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

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

相关文章

算法思想总结:双指针算法

一、移动零 . - 力扣&#xff08;LeetCode&#xff09; 移动零 该题重要信息&#xff1a;1、保持非0元素的相对位置。2、原地对数组进行操作 思路&#xff1a;双指针算法 class Solution { public:void moveZeroes(vector<int>& nums){int nnums.size();for(int cur…

Elasticsearch:在本地使用 Gemma LLM 对私人数据进行问答

在本笔记本中&#xff0c;我们的目标是利用 Google 的 Gemma 模型开发 RAG 系统。 我们将使用 Elastic 的 ELSER 模型生成向量并将其存储在 Elasticsearch 中。 此外&#xff0c;我们将探索语义检索技术&#xff0c;并将最热门的搜索结果作为 Gemma 模型的上下文窗口呈现。 此外…

人工智能迷惑行为大赏!

目录 人工智能迷惑行为大赏 一&#xff1a;人工智能的“幽默”瞬间 1. 图像识别出现AI的极限 2. 小批量梯度下降优化器 3. 智能聊天机器人的冰雹问题 4. 大语言模型-3经典语录 二&#xff1a;技术原理探究 1. 深度学习 2. 机器学习 3. 自然语言处理 4. 计算机视觉 三…

java八股文 笔记(持续更新中~)

1 Redis 2Mysql 3JVM 4java基础底层 5 spring 6 微服务 7.......(持续更新) One:Redis篇 1.穿透 2&#xff1a;击穿 3&#xff1a;雪崩 3 33 4:双写一致 5.持久化 2 JVM: 2&#xff1a; 3&#xff1a; 4&#xff1a; 5&#xff1a; 6&#xff1a; 7&#xff…

学生时期学习资源同步-1 第一学期结业考试题1

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载

深度学习--离线数据增强

最近做项目遇见数据集背景非常单一&#xff0c;为了增加模型的返回能里&#xff0c;只能自己做一些数据增强来增加背景的多样性。代码如下&#xff1a; import numpy as np import cv2def create_mask(box, height, width):"""创建一个全零的掩码图像&#xff…

Prompt进阶2:LangGPT(构建高性能Prompt策略和技巧)--最佳实践指南

Prompt进阶2:LangGPT(构建高性能Prompt策略和技巧)–最佳实践指南 0.前言 左图右图 prompt 基本是一样的&#xff0c;差别只在提示工程这个词是否用中英文表达。我们看到&#xff0c;一词之差&#xff0c;回答质量天壤之别。为了获得理想的模型结果&#xff0c;我们需要调整设…

uniapp开发DAPP钱包应用(二) Vue + Java

上一节我们讲了如何通过vue uniapp还有web3以及需要准备的相关组件&#xff0c;来搭建了DAPP开发的环境。 这一节&#xff0c;我们来说说如何用代码来实现DAPP相关接口。 1. ethers实现类 导入组件 import { ethers , providers , utils } from "ethers"; impor…

跟着GPT学设计模式之桥接模式

说明 桥接模式&#xff0c;也叫作桥梁模式&#xff0c;英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中&#xff0c;桥接模式是这么定义的&#xff1a;“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就…

我真是服了!你们刚开始学习的时候也是造火箭吗?能不能有一个简单的纯纯纯html模板给我学学,真的看不懂好嘛!

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

弧形导轨的设计要求

制造业设备种类越来越多&#xff0c;非标自动化设备渐渐成了主力市场&#xff0c;其中弧形导轨线体作为非标自动化运输中的基石&#xff0c;承担了运输&#xff0c;定位&#xff0c;特殊工位组装&#xff0c;其设计要求也非常严格。 1、精度要求&#xff1a;弧形导轨需要具备高…

大数据 - HBase《一》- Hbase基本概念

目录 1.1. Hbase简介 1.2 Hbase,Hive, Mysql对比 1.3 Hbase数据模型 &#x1f959;region(区域) &#x1f959;rowkey(行键) &#x1f959;列族&#xff08;column family) &#x1f959;列&#xff08;column Qualifier) &#x1f959;版本&#xff08;version)-默认按…

如何仅用3行代码,搞定业务敏感数据加解密?

01 引子&#xff1a;一个数据安全的故事 一个风和日丽的早上&#xff0c;某家快递物流公司内。 &#xfeff;张老板看着电脑屏幕&#xff0c;眉头紧锁。电脑屏幕上赫然写着&#xff0c;“疑似45亿条个人信息泄露&#xff0c;电商物流行业数据安全警铃再响”。据传&#xff0c;…

代码学习记录18

随想录日记part18 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.13 主要内容&#xff1a;今天的主要内容是二叉树的第七部分&#xff0c;主要涉及二叉搜索树的最近公共祖先 &#xff1b;二叉搜索树的最近公共祖先&#xff1b;删除二叉搜索树中的节点 。 23…

国内使用GPT4的5种解决方案,最后一个是全场最佳

ChatGPT4是目前世界上最先进的自然语言处理模型 大家都知道ChatGPT4特别好用 我个人来说&#xff0c;基本上每天都会用GPT来查资料、写代码和润色文章 但是在国内&#xff0c;使用ChatGPT4&#xff0c;是有一定门槛的 门槛一 mo法问题 ChatGPT的网站&#xff0c;国内是无法访问…

javaEE13(网站第8章两个课后题)

1、对“jspservletjavabean实现分页查询”功能做如下补充&#xff1a; &#xff08;1&#xff09;记录批量删除&#xff1a;每个记录前添加复选框&#xff0c;点击批量删除&#xff0c;删除选中记录。 增加跳转到任意页功能。用户可改变每页记录条数。 页面&am…

ImportError: Plotly express requires pandas to be installed.

在 Python3 环境下&#xff0c;使用 plotly 绘图时&#xff0c;发生了如下错误&#xff1a; ImportError: Plotly express requires pandas to be installed. 通过排查发现是使用了折行导入时报错的&#xff1a; import plotly.express as px 通过检索找到了解决办法&#xff0…

VScode Error Lens插件

安装完成之后&#xff0c;当我们输入一些错误的语法格式的时候&#xff0c;它都会有一些提示&#xff01; 一开始是英文提示 修改为中文提示 设置搜索 typescript.local

【gpt实践】李某的AI课程值199吗

先说个人的答案&#xff1a;不值。但也不是说毫无价值&#xff0c;只是他的价值没那么高。 文末分享该课程&#xff0c;大家有兴趣可以看看&#xff0c;该课程是否有价值。 “清华博士”推出的199元的AI课程销售额竟然突破了5000万。这一数字让人惊叹&#xff0c;也引发了人们…

免费AI软件开发工具测评:iFlyCode VS CodeFlying

前言 Hello&#xff0c;各位看官&#xff0c;今天为大家带来两款人工智能的软件开发工具的测评&#xff0c;他们分别是iFlyCode和CodeFlying&#xff0c;我相信当大家看到这两款产品名字的时候不禁都会有些好奇&#xff0c;两个产品都有Code 和Fly两个元素&#xff0c;那他们之…