【c++】vector模拟

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕vector模拟

> 毒鸡汤:在等待的日子里,刻苦读书,谦卑做人,养得深根,日后才能枝叶茂盛。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

        我们已经了解了vector的知识点,学完知识点必然需要来手搓一个vector,这样对知识点掌握更加熟练,如果大家写过string模拟的话,手撕vector就更加容易些😚😚。那咱们话不多说进入今天的主题---->【c++】vector模拟。

 ⭐主体

这里我们就不分解成三文件啦,这里就创建两个文件vector.h(头文件),test.c(测试代码文件)

而我们今天就不像string模拟一样啦,咱们按照下面的方式来模拟vector。

🌙基本框架结构

为了避免和库里的vector产生冲突,在自己的命名空间内实现vector

namespace lyk
{
    template<class T>//通过模板能够实现不同类型的数据存储
	class vector
	{
	public:
	    typedef	T* iterator;
 
        /*
            各类函数功能实现
        */
 
	private:
		iterator _start;          //指向容器中的起始位置
		iterator _finish;         //指向容器中最后一个有效数据的下一个位置
		iterator _end_of_storage; //指向容器中现有容量的位置
	};
}

这里我们需要简单了解成员变量的作用,以下面的图解来解析作用:



🌙默认成员函数

这个板块中我们得讲解vector的初始化,销毁.....

💫构造函数

在构造函数中无非就是初始化列表,简单来讲是实现初始化。

💦无参构造函数

无参的构造函数,把三个成员变量设置为空指针。

//构造函数 --- 无参构造
vector()
	//初始化成员列表
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{}
💦有参构造函数1

迭代器区间构造函数,由于不确定元素类型我们就用模板来解析元素类型,之后再尾插。

//构造函数2
template<class InputIterator> //模板函数
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	//将迭代器区间在[first,last)的数据一个个尾插到容器当中
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

实例:

int a[5] = { 1,2,3,4,5 };
vector<int>v1(a, a + 5);

💦有参构造函数2

该容器当中含有n个值为val的数据。将容器容量先设置为n,尾插n个值为val的数据。

//构造函数3
vector(size_t n, const T& val)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n); //调用reserve函数将容器容量设置为n
	for (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中
	{
		push_back(val);
	}
}

💫析构函数

在析构函数中无非就是销毁数据,简单来讲释放内存。

~vector() // 析构函数(销毁)
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _endofstorage = nullptr;
	}
}

💫拷贝构造

对于拷贝构造函数,由于涉及到深浅拷贝的问题,我们这里提供传统写法与现代写法。

💦传统写法

咱们先看看思路:

  1. 先开辟一块与该容器大小相同的空间。
  2. 然后将该容器当中的数据一个个拷贝过来即可。
  3. 最后更新_finish和_endofstorage的值即可。

1.

//传统写法2.
//出现深浅拷贝问题
//v2(v1) 
vector(const vector<T>& v)  // 拷贝构造
{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, v.size()* sizeof(T));
	_finish = _start + v.size();
	_endofstorage = _start + v.capacity();
}

2.

//传统写法2.
//解决深浅拷贝问题
vector(const vector<T>& v)
{
	_start = new T[v.capacity()]; //让v2开辟一块和v1一样大小的空间
	for (size_t i = 0; i < v.size(); i++)
	{
		_start[i] = v[i];//通过循环进行赋值
	}
	//最后调整_finish和_end_of_storage的大小
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();

}

在这里就会出现一个问题:出现深浅拷贝问题。

  • 写法1对于数据的拷贝采用的是memcpy函数。
  • 写法2对于数据的拷贝采用的是for循环进行赋值拷贝。

两者在拷贝数据的方式上对于内置类型或不需要进行深拷贝的自定义类型,完全是满足要求的,但是当vector存储的是string时,一定存在问题。

采用图解:

存储了5个数据,每个数据都是string类,vector<string>v2(v1),v2也开辟了5个空间。

  • 写法1,在memcpy下完成拷贝,但是它们却指向了同一块空间,在调用析构函数时,就会导致同一块空间释放多次,最终内存泄露。
  • 写法2,它会去调用string类的赋值重载函数进行一个深拷贝。
💦现代写法
  • 使用范围for(或是其他遍历方式)对容器v进行遍历。
  • 在遍历过程中将容器v中存储的数据一个个尾插过来即可。
//现代写法
// v2(v1)
vector(const vector<T>& v) // 拷贝构造
{
	reserve(v.capacity());   // 判断是否需要扩容
	for (const auto& e : v)  
	{
		push_back(e);        // 拷贝尾插
	}
}

💫赋值运算符重载函数

 赋值运算符重载的进行是深拷贝,是将深拷贝出来的对象与左值进行了交换。

// v1 = v3
vector<T>& operator=(vector<T> v) // 赋值运算重载
{
	swap(v);
	return *this;
}

这里也有传统写法和现代写法,博主这里是现代写法,有兴趣的小伙伴们大家可以写写传统写法。

💫析构函数

首先判断该容器是否为空容器。

  • 若为空容器,则无需进行析构操作。
  • 若不为空,则先释放容器存储数据的空间。

然后将容器的各个成员变量设置为空指针即可。

代码实现:

~vector() // 析构函数(销毁)
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _endofstorage = nullptr;
	}
}

🌙迭代器相关的函数

begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址。

iterator begin()  // 返回容器的首地址
{
	return _start;
}

iterator end()    // 返回容器当中有效数据的下一个数据的地址
{
	return _finish;
}

const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改。

const_iterator begin() const  // 返回容器的首地址
{
	return _start;
}

const_iterator end() const  //返回容器当中有效数据的下一个数据的地址
{
	return _finish;
}

小试牛刀:

int main()
{
	vector<int> v{ 1,2,3,4,5 };
	//范围for进行遍历
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	
	return 0;
}

运行结果:

🌙容量相关的函数

💫size函数和capacity函数

咱们再看看这张图:

这两个指针之间对应类型的数据个数,所以size可以由_finish - _start得到,而capacity可以由_endofstorage - _start得到。

size_t size() const      // 返回容器当中有效数据的个数
{
	return _finish - _start;
}

size_t capacity() const  // 返回当前容器的最大容量 
{
	return _endofstorage - _start;
}

💫reserve函数

reserve函数:

  • 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  • 当n小于对象当前的capacity时,不进行操作。
void reserve(size_t n)   // 判断扩容
{
	if (n > capacity())
	{
		size_t old = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, old * sizeof(T));
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + old;
		_endofstorage = _start + n;
	}
}

首先得算好增容前的数据个数,因为增容完后,就需要释放旧空间。

1.如果没有对增容前的数据个数进行记录: 

2.如果增容前后的数据拷贝使用memcpy:

💫resize函数

resize规则:

  • 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  • 当n小于当前的size时,将size缩小到n。
// 判断缩括容
// T()是匿名对象,它的生命周期本来只是存在于当前行,
// 但是被const修饰以后,可以延长它的生命周期
void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

💫empty函数

通过比较容器当中的_start和_finish指针的指向,若所指位置相同且为空,则该容器为空。

bool empty()const
{
	return _start == _finish;
}

🌙增删查改相关的函数

💫push_back函数

  1. 首先得判断容器是否已满。
  2. 若已满则需要先进行增容。
  3. 然后将数据尾插到_finish指向的位置。
  4. 再将_finish++。
void push_back(const T& x)  // 尾插
{
	if (_finish == _endofstorage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}

	*_finish = x;
	++_finish;
}

💫pop_back函数

  1. 先判断容器是否为空
  2. 若为空则做断言处理
  3. 若不为空则将_finish--。
void pop_back() // 尾删
{
	assert(size() > 0);
	--_finish;
}

小试牛刀:

//测试一
void test_vector1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;

	vector<int>::iterator it1 = v.begin();
	while (it1 != v.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

int main()
{
    test_vector1();
    return 0;
}

运行结果:

💫insert函数

insert函数可以在指定的pos位置插入数据。

  1. 在插入数据前先判断是否需要增容。
  2. 然后将pos位置及其之后的数据统一向后挪动一位。
  3. 最后将数据插入到pos位置。
void insert(iterator pos, const T& x) // 某个位置插入数据
{
	assert(pos >= _start && pos <= _finish);

	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
	*pos = x;

	++_finish;
}

💫erase函数

erase函数可以删除所给迭代器pos位置的数据

  1. 在删除数据前需要判断容器释放为空。
  2. 若为空则需做断言处理。
  3. 删除数据时直接将pos位置之后的数据统一向前挪动一位。
  4. 将pos位置的数据覆盖即可。
iterator erase(iterator pos) // 删除所给迭代器pos位置的数据
{
	assert(pos >= _start);
	assert(pos < _finish);
	iterator begin = pos + 1;
	while (begin < _finish)
	{
		*(begin - 1) = *begin;
		++begin;
	}
	--_finish;
	return pos;
}

💫swap函数

swap函数简单来说就是交换两个容器的数据。

void swap(vector<T>& v) // swap函数用于交换两个容器的数据
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

💫print_vector函数

print_vector函数来打印我们容量的数据。

void print_vector(const vector<int>& v) // 打印
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

🌙访问容器相关函数

这里有两个操作符重载函数:

  • 第一个可以读也可以修改。
  • 第二个只可以读不可以修改。
T& operator[](size_t i)
{
	assert(i < size()); //检测下标的合法性

	return _start[i]; //返回对应数据
}

const T& operator[](size_t i)const
{
	assert(i < size()); //检测下标的合法性

	return _start[i]; //返回对应数据
}

 🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态规划(分割等和子集)

416. 分割等和子集 题目难易&#xff1a;中等 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200 示例 1: 输入: [1, 5, 11, 5] 输出: true 解释: 数…

STM32入门教程-2023版【3-3】gpio输入

关注 星标公众号 不错过精彩内容 大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! 上两小节我们已经把GPIO的结构和8种输入输出模式都讲完了&#xff0c;到这里还不懂的可以回…

浅析内存一致性:内存屏障

文章目录 概述内存乱序访问Store Buffer和Invalidate QueueStore BufferStore ForwardingStore Buffer与内存屏障 Invalidate QueueInvalidate Queue与内存屏障 内存屏障分类编译器屏障CPU内存屏障 相关参考 概述 内存屏障&#xff0c;是一类同步屏障指令&#xff0c;是CPU或编…

Java中的输入输出处理(一)

文件 文件&#xff1a;文件是放在一起的数据的集合。比如1.TXT。 存储地方&#xff1a;文件一般存储在硬盘&#xff0c;CD里比如D盘 如何访问文件属性&#xff1a;我们可以通过java.io.File类对其处理 File类 常用方法&#xff1a; 方法名称说明boolean exists()判断文件或目…

处理机调度与死锁

目录 进程调度算法先来先服务调度算法FCFS最短作业优先调度算法SJF最高优先级调度算法***HPF***高响应比优先调度算法 ***HRRN***时间片轮转调度算法***RR***多级队列调度算法MFQ 进程调度算法 进程调度算法也称为CPU调度算法 当 CPU 空闲时&#xff0c;操作系统就选择内存中…

一天一个设计模式---工厂方法

概念 工厂模式是一种创建型设计模式&#xff0c;其主要目标是提供一个统一的接口来创建对象&#xff0c;而不必指定其具体类。工厂模式将对象的实例化过程抽象出来&#xff0c;使得客户端代码不需要知道实际创建的具体类&#xff0c;只需通过工厂接口或方法来获取所需的对象。…

uniapp中uview组件库丰富的Table 表格的使用方法

目录 #平台差异说明 #基本使用 #兼容性 #API #Table Props #Td Props #Th Props 表格组件一般用于展示大量结构化数据的场景 #平台差异说明 AppH5微信小程序支付宝小程序百度小程序头条小程序QQ小程序√√√√√√√ #基本使用 本组件标签类似HTML的table表格&#…

模型评估:评估指标的局限性

“没有测量&#xff0c;就没有科学。”这是科学家门捷列夫的名言。在计算机科学特别是机器学习领域中&#xff0c;对模型的评估同样至关重要。只有选择与问题相匹配的评估方法&#xff0c;才能快速地发现模型选择或训练过程中出现的问题&#xff0c;迭代地对模型进行优化。模型…

猫头虎分享:Linux 如何安装最新版的Docker和Docker-Compose 教程 ‍

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

教你如何将本地虚拟机变成服务器,供其它电脑访问

场景&#xff1a;最近在做数据仓库的作业&#xff0c;需要团队协作&#xff0c;买不起阿里云服务器&#xff0c;所以想到能不能将我本地机上的虚拟机变成服务器&#xff0c;供其它同学的电脑访问。在虚拟机上安装hadoop和hive&#xff0c;然后同学机子上安装kettle进行连接。最…

书生大模型全链路开源体系

书生浦语大模型全链路开源体系开源了哪些东西 数据书生万卷&#xff1a;一个2TB的涵盖多种模态与任务的数据集预训练InternLM-Train&#xff1a;微调XTuner&#xff1a;可供你低成本微调模型的工具箱部署LMDeploy&#xff1a;一个服务端场景下、transformer 结构 LLM 部署工具…

【模拟IC学习笔记】Cascode OTA 设计

辅助定理 增益Gm*输出阻抗 输出短路求Gm 输入置0求输出阻抗 求源极负反馈的增益 随着Vin的增加&#xff0c;Id也在增加&#xff0c;Rs上压降增加&#xff0c;所以&#xff0c;Vin的一部分电压体现在Rs上&#xff0c;而不是全部作为Vgs&#xff0c;因此导致Id变得平滑。 Rs足…

Python书籍推荐,建议收藏

学习Python的书籍可太多了&#xff0c;从入门到放弃&#xff0c;应有尽有啊 入门书籍 根据豆瓣评分的高低&#xff0c;这里介绍了一些经典入门书籍&#xff0c;大家根据自身情况选择尝试 《Python编程&#xff1a;从入门到实践&#xff08;第二版&#xff09;》 非常经典且非…

搜维尔科技:第九届元宇宙数字人设计大赛作品规范解读!

作品提交 参赛小组需要将作品上传至百度网盘&#xff0c;并将分享链接发送至frankaxis3d.cn邮箱。邮寄格式如下&#xff1a; 邮件标题&#xff1a;作品名称元宇宙数字人设计大赛作品 邮件内容标明&#xff1a;学校名称、院系名称、作品名称、作者名称、联系电话及指导老师名…

vue中鼠标拖动触发滚动条的移动

前言 在做后端管理系统中&#xff0c;像弹窗或大的表单时&#xff0c;经常会有滚动条的出现&#xff0c;但有些时候如流程、图片等操作时&#xff0c;仅仅使用鼠标拖动滚动条操作不太方便&#xff0c;如果使用鼠标拖拽图片或容器来触发滚动条的移动就比较方便了 功能设计 如…

【leetcode】力扣算法之删除链表中倒数第n个节点【中等难度】

删除链表中倒数第n个节点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 用例 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 输入&#xff1a;head …

蓝牙模块在电动汽车充电设施中的创新应用

随着电动汽车的普及&#xff0c;充电设施的便捷性和智能化成为关键的发展方向。蓝牙技术作为一种无线通信技术&#xff0c;在电动汽车充电设施中发挥着越来越重要的作用。本文将深入探讨蓝牙模块在电动汽车充电设施中的创新应用&#xff0c;以提高充电体验、提升管理效率&#…

“程序员面试之道:成为求职战场上的不可忽视的力量“

文章目录 每日一句正能量前言面试经历面试技巧后记 每日一句正能量 看淡拥有&#xff0c;不刻意追求某些东西&#xff0c;落叶归根&#xff0c;那些属于你的&#xff0c;总会回来。 前言 在现代科技发展日新月异的时代&#xff0c;程序员无疑扮演着重要的角色。他们是代码的创…

我的1827创作纪念日

机缘 习惯性早上打开电脑&#xff0c;看看CSDN上的资讯&#xff0c;了解行业动态、当前新的技术和大佬的分享。自己动手写应该是2019 年 01 月 08 日&#xff0c;当时应该是在用安装和使用Oracle&#xff0c;遇到一些问题&#xff0c;写下第一篇博客 Oracle存储过程常见问题及…

经典算法-遗传算法的解走迷宫例子

经典算法-遗传算法的一个简单例子 使用遗传算法走迷宫&#xff0c;如果能从起点顺利走到终点&#xff0c;就能获胜。 迷宫如下图所示&#xff0c;绿点为迷宫起点&#xff0c;橙色点为迷宫终点。 LLM大模型相关文章&#xff1a; 大模型查询工具助手之股票免费查询接口 GPT实…