vector 简单模拟实现

news2024/9/21 12:40:43

 

目录

一. vector成员变量

 二. vector的构造函数和析构函数

三. vector的成员函数

1. 容量操作函数

扩容操作

(1). finish更新问题

(2). 扩容深浅拷贝问题

 resize与尾插、尾删与判空

 insert与erase与clear

2. 函数重载

(1). 赋值运算符重载

(2). [ ]重载进行访问

四. 完整代码


 vector的底层实现主要依赖于动态数组,特点是可以根据需要自动扩展和收缩

我们在这里将其封装到一个命名空间中来实现

一. vector成员变量

vector成员变量是三个迭代器,这里我们用指针typedef实现

#include<iostream>
#include<assert.h>
#include<cstring>
using  namespace std;
namespace Pc
{
	template<typename T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
        //......
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

我们再实现begin()和end()来返回迭代器(代码量小的都是在类内定义的)

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

 二. vector的构造函数和析构函数

分别是默认构造函数,用迭代器构造,还有构造将n个val给予数组以及拷贝构造函数

		vector()//什么也不写,成员变量走了初始化列表
		{

		}
		//vector() = default;//关键字,强制生成构造函数
		template<typename 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);
			}
		}
		vector(const vector<T>& v)
		{
			reserve(size());
			for (auto& e : v)
			{
				push_back(e);//直接尾插即可
			}

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

const T& val = T(),给缺省值使自定义类型走构造函数,内置类型int等也有默认构造的概念(当然也有析构)

如下代码

		int i = int(); //内置类型int等也有默认构造的概念,当然也有析构
		int j = int(1);
		int k(2);

为什么使用迭代器进行构造使用模版函数呢,因为这样就可以使用任意类型的迭代器初始化,要求类型匹配,但这样也有问题

例如以下测试用例

	void test_vector5()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(2);
		v1.push_back(2);
		vector<int> v2(v1.begin() , v1.end());
		vector_printf(v1);
		vector_printf(v2);

		list<int> lt;
		lt.push_back(10);
		lt.push_back(10);
		lt.push_back(10);
		lt.push_back(10);
		vector<int> v3(lt.begin(), lt.end());

		vector<string> v4(10, "11111");
		vector<string> v5(10, "");
		vector_printf(v3);
		vector_printf(v4);
		vector_printf(v5);

		vector<int> v6(10, 1);//因为两个参数一样会调用vector(InputIterator first,InputIterator last)
	//而非 vector(size_t n, const T& val = T())
		//解决方法1. 将第一个参数强转,2. 将函数重载第一个参数为int类型 
		vector_printf(v6);
	}

因为两个参数一样会调用vector(InputIterator first,InputIterator last)而非 vector(size_t n, const T& val = T())

此时我们我们可以

1. 将第一个参数强转

2. 将函数重载第一个参数为int类型,即我们上面的代码

三. vector的成员函数

1. 容量操作函数
扩容操作
		size_t size() const//求元素个数
		{
			return _finish - _start;
		}
		size_t capacity() const//求vector容量
		{
			return _end_of_storage - _start;
		}
        void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = _finish - _start;
				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * size());//浅拷贝,string之类的释放就为随机值了
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i]= _start[i] ; 
				}
				delete[] _start;
				_start = tmp;
				_finish = old_size + _start;
				_end_of_storage = _start + n;
			}
		}
(1). finish更新问题

在开辟新空间后原有的空间就释放了所以我们不可以在释放空间后将_finish的值更新写为

_finish=tmp+size();

 因为size()的实现是要用到已经被释放的_finish的,我们可以

1. 将_finish的更改写到delete[] 即释放空间前面

2. 用一个数记录size(),使_finish再空间释放后加这个数

(2). 扩容深浅拷贝问题

这里我们为什么不用memcpy呢

1. memcpy是内存的二进制格式拷贝将一段内存空间中的内容原封不动的拷贝到另外一段内存空间中

2. . 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型 元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝

 比如存储的是string或者是vector,进行了浅拷贝后就被释放了,那相当于存储了几个被释放的空间

如果对象中涉及到资源管理时,一定不能用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃


 resize与尾插、尾删与判空
		//自定义类型会调用默认构造,为了兼容 内置类型int等也有默认构造的概念
		void resize(size_t n, T val = T())//const T& val=T()
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(!empty());
			_finish--;
		}
        bool empty()
		{
			return _start == _finish;
		}

上面是resize是调整容器大小使其包含n个元素

n<容器大小则内容将减少到其前 n 个元素,并删除超出其范围的元素(并销毁它们)

n 大于当前容器大小,则通过在末尾插入尽可能多的元素来扩展内容,以达到 n 的大小

大于容器大小会扩容到指定大小

剩下的为尾插尾删和判断vector是否为空 


 insert与erase与clear
		void insert(iterator pos, const T& x)
		{
			assert(pos - _start <= size());
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			iterator end = _finish;
			while (end > pos)
			{
				*end = *(end - 1);
				end--;
			}
			*end = x;
			_finish++;

		}
		void erase(iterator pos)
		{
			assert(pos - _finish < 0);
			iterator end = pos;
			while (end != _finish)
			{
				*end = *(end + 1);
				end++;
			}
			_finish--;
		}
		void clear()
		{
			_finish = _start;
		}

需要注意的还是迭代器失效问题,如果使用迭代器需要在扩容操作后及时更新

2. 函数重载
(1). 赋值运算符重载
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)//若,地址相同说明是自己给自己赋值,什么也不用干
		//	{
		//		clear();
		//		reserve(v.size());
		//		for (auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	//if (this != &v)
		//	//{
		//	//	vector<T> tmp(v);
		//	//	swap(tmp);
		//	//}
		//	return *this;
		//}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}
		//vector& operator=(vector v)//类内允许这样,类外不可以
		vector<T>& operator=(vector<T> v)
		{
			swap(v);

			return *this;
		}

我们有两种方法三种方法

1. 参数不传引用通过自己实现的交换函数,与v这个拷贝构造的对象交换,v在函数结束生命周期就结束了

2. 参数传引用判断赋值的是不是自己,是就直接返回,不是就清空vector后一个个尾插

3. 参数传引用,手动拷贝构造一个vector类对象与自身交换

(2). [ ]重载进行访问

实现可读可写与可读不可写两种,返回引用减少拷贝消耗

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

四. 完整代码

#include<iostream>
#include<assert.h>
#include<cstring>
#include<list>
using  namespace std;
namespace Pc
{
	template<typename T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		vector()//什么也不写,成员变量走了初始化列表
		{

		}
		//vector() = default;//关键字,强制生成构造函数
		template<typename 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);
			}
		}
		vector(const vector<T>& v)
		{
			//reserve(size());
			for (auto& e : v)
			{
				push_back(e);//直接尾插即可
			}

		}
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)//若,地址相同说明是自己给自己赋值,什么也不用干
		//	{
		//		clear();
		//		reserve(v.size());
		//		for (auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	//if (this != &v)
		//	//{
		//	//	vector<T> tmp(v);
		//	//	swap(tmp);
		//	//}
		//	return *this;
		//}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}
		//vector& operator=(vector v)//类内允许这样,类外不可以
		vector<T>& operator=(vector<T> v)
		{
			swap(v);

			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = _finish - _start;
				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * size());//浅拷贝,string之类的释放就为随机值了
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i]= _start[i] ; 
				}
				delete[] _start;
				_start = tmp;
				_finish = old_size + _start;
				_end_of_storage = _start + n;
			}
		}
		bool empty()
		{
			return _start == _finish;
		}

		//自定义类型会调用默认构造,为了兼容 内置类型int等也有默认构造的概念
		void resize(size_t n, T val = T())//const T& val=T()
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(!empty());
			_finish--;
		}
		void insert(iterator pos, const T& x)
		{
			assert(pos - _start <= size());
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			iterator end = _finish;
			while (end > pos)
			{
				*end = *(end - 1);
				end--;
			}
			*end = x;
			_finish++;

		}
		void erase(iterator pos)
		{
			assert(pos - _finish < 0);
			iterator end = pos;
			while (end != _finish)
			{
				*end = *(end + 1);
				end++;
			}
			_finish--;
		}
		void clear()
		{
			_finish = _start;
		}
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

这篇就到这里啦

(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

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

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

相关文章

phpstudy搭建sqlilabs本地靶场

请先在网上解决好前置条件的下载和安装&#xff1a;phpstudy、vscode、navicat premium(非必要)、sqlilab的压缩包/文件夹 phpstudy--安装sqlilabs 1.打开phpstudy后&#xff0c;我们会用到MySQL5.7.26和Nginx1.15.11 #mysql5.7.26是因为sqlilabs靶场不支持高版本MySQL 2.在软…

练习实践 web中间件httpd-id:2-编译安装-web登录认证设置

参考来源&#xff1a; 用编译的方式安装apache httpd服务 编译安装过程记录 1.下载准备环境&#xff1a; yum -y install gcc gcc-c make pcre pcre-devel gd-devel openssl-devel zlib zlib-devel apr-*根据之前的操作文档和实际安装经验&#xff0c;提前将所需依赖项安装…

sqli-labs注入练习1,2关

sqli-labs第一关 判断是否存在sql注入 可见&#xff0c;根据输入数字的不同&#xff0c;结果也不同 判断sql语句是否是拼接&#xff0c;且是字符型还是数字型 由上可见&#xff0c;这关指定是字符型且存在sql注入漏洞 使用联合注入 第一步&#xff1a;首先知道表格有几列&…

PyCharm 2024.1 最新变化

文章目录 PyCharm 2024.1 最新变化一、新的 AI Assistant 功能 PyCharm Professional1、一键创建包含生成代码的文件2、生成架构感知型 SQL 查询 二、缩小整个 IDE 的选项三、新终端 Beta PyCharm 2024.1 最新变化 pycharm是什么 作为 JetBrains 旗下的一款专为 Python 开发者设…

SQL注入之sqli-labs靶场第二关

手工注入less-2 1.找注入点 通过 ?idsdfsdf 报错发现注入点 经过尝试没有闭合发现是数字型注入 2.猜解字段数量 发现字段数量为3 3.通过union联合查询判断回显点 发现回显点2&#xff0c;3&#xff0c; 4&#xff0c;进行信息收集 数据库版本号&#xff1a;5.7.26 数据库…

【MongoDB】1.MongoDB下载与安装

目录 一、下载 二、安装 三、安装MongoDB Compass 四、连接 一、下载 官网地址&#xff1a; https://www.mongodb.com/download-center/community 二、安装 详细的安装教程可参考&#xff1a; MongoDB安装&#xff08;超详细&#xff09;_安装mongodb-CSDN博客 注意事项1&…

亚马逊爬虫(Amazonbot)IP地址,真实采集数据

一、数据来源&#xff1a; 1、这批亚马逊爬虫&#xff08;Amazonbot&#xff09;IP来源于尚贤达猎头公司网站采集数据&#xff1b; ​ 2、数据采集时间段&#xff1a;2023年10月-2024年7月&#xff1b; 3、判断标准&#xff1a;主要根据用户代理是否包含“Amazonbot”和IP核…

C# Unity 面向对象补全计划 七大原则 之 依赖倒置原则 (DIP)难度:☆☆ 总结:多抽象,多接口,少耦合

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列作为七大原则和设计模式的进阶知识&#xff0c;看不懂没关系 请看专栏&#xff1a;http://t.csdnimg.cn/mIitr&#xff0c;查漏补缺 1.依赖倒置原则 (DIP) 这…

【算法题】无重复字符的最长子串(滑动窗口)

目录 一、题目描述 二、解题思路 1、什么是滑动窗口算法&#xff1f; 2、滑动窗口一般解题模板 三、参考答案 一、题目描述 无重复字符的最长子串 给定一个字符串s &#xff0c;请你找出其中不含有重复字符的最长子串的长度。 示例 1: 输入: s "abcabcbb"…

【Linux】vim(工具篇)

文章目录 什么是vimvim的使用普通模式&#xff08;Normal Mode&#xff09; 命令模式&#xff08;Command Mode&#xff09;批量化注释/批量化去注释 vim的配置 什么是vim Vim 是一种高度可配置的文本编辑器&#xff0c;最初由 Bram Moolenaar 在 1991 年基于 vi 编辑器创建。V…

力扣——238.移动零

题目 思路 利用双指针&#xff0c;先找到第一个为0的地方指向&#xff0c;指针2指向下一个&#xff0c;指针1之前是已经处理好的数据&#xff0c;指针2进行遍历&#xff0c;遇到非零则与指针1数据交换&#xff0c;然后指针1。 代码 class Solution { public:void moveZeroes(…

OpenAI not returning a result?

题意&#xff1a;OpenAI 没有返回结果吗&#xff1f; 问题背景&#xff1a; Im trying to use the OpenAI beta but I cant seem to get a result. Im accessing the API via an NPM package (openai-api - npm). I have that setup and working but when I make a request th…

Unity强化工程 之 Mask SortingGroup

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 1.Mask 遮罩故名思意就是起到遮挡作用的罩子:精灵遮罩 - Unity 手册 如果我想让sprite与遮罩发生交互&#xff0c;那么我…

深入理解接口测试:实用指南与最佳实践(三)API文档解析及编写测试用例

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这一阶段是接口测试的学习&#xff0c;我们接下来的讲解都是使用Postman这款工具&#xff0c;当然呢Postman是现在一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用…

人工智能大模型 | 通俗讲解AI基础概念

LLM LLM&#xff08;Large Language Models&#xff09;指的是大型语言模型。这些模型是自然语言处理&#xff08;NLP&#xff09;技术的一部分&#xff0c;使用深度学习训练来理解、生成、翻译文本&#xff0c;甚至执行特定的语言相关任务&#xff0c;如问答、文本摘要、编程…

技术方案、实施例和图纸应该怎么写?

技术方案、实施例和图纸应该怎么写&#xff1f;

【Vue3】组件通信之mitt

【Vue3】组件通信之mitt 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日…

【如何高效处理前端常见问题:策略与实践】

在快速发展的Web开发领域&#xff0c;前端作为用户与应用程序直接交互的界面&#xff0c;其重要性不言而喻。然而&#xff0c;随着技术的不断演进和项目的复杂化&#xff0c;前端开发者在日常工作中难免会遇到各种挑战和问题。本文旨在深入探讨前端开发中常见的问题类型&#x…

【竞技宝jjb.lol】奥运会:樊振东夺金证明自己

北京奥运会乒乓球男子单打决赛结束,中国球员樊振东在决赛中4比1击败了瑞典天才莫雷加德,为中国队保下了这枚关键的金牌。樊振东也成为了继马龙之后,第二位同时拿到奥运会、世锦赛、世界杯、亚运会、亚锦赛、亚洲杯、巡回赛总决赛、全运会单打冠军的球员。作为国乒男队一哥,樊振…

嵌入式day19

文件 标准io&#xff1a;stdio.h&#xff08;计算机角度做输入输出&#xff09;&#xff08;读写文件&#xff09; 标准io概念 IO库&#xff0c;c语言的标准 IO &#xff1a;input output I &#xff1a;键盘是标准输入设备 》默认输入就是指键盘 /dev/input O&#xff1a…