【C++】vector的模拟实现

目录

  • 1.vector的结构
  • 2.构造函数
    • 2.1 无参构造
    • 2.2 以迭代器区间作为参数的构造函数
    • 2.3 构造n个value值
  • 3.拷贝构造
    • 3.1 传统写法
    • 3.2 现代写法
  • 4.赋值重载
  • 5.迭代器失效问题
    • 5.1 reserve和resize
    • 5.2 insert
  • 5.3 erase
  • 4. 整体代码(包含迭代器、析构函数等)

1.vector的结构

vector的结构体由三个迭代器类型组成,分别是:
指向第一个元素的_start,
指向最后一个元素下一个位置的_finish,
指向空间末尾下一个位置的_end_of_storage。

namespace lgr
{
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		......
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

在这里插入图片描述

2.构造函数

2.1 无参构造

无参构造可以直接用初始化列表将三个成员变量初始化为nullptr

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{ }

或者我们可以在成员变量声明时初始化,这样无参构造就不用写内容了

namespace lgr
{
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		
		vector()
		{ } 
		
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

2.2 以迭代器区间作为参数的构造函数

vector还支持用其他的容器来初始化,参数可以是vector类型的迭代器,也可以是其他容器类型的迭代器,所以需要用模板来实现。

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

2.3 构造n个value值

vector支持用n个value值进行构造,其中的value既可以是内置类型也可以是自定义类型,要注意v用引用做参数时要用const修饰,否则无法接收常量值。

vector(size_t n, const T& val = T())
{
	reserve(n);		// 后面会实现,先用着
	for (int i = 0; i < n; ++i)
	{
		push_back(val); 
	}
}

但是当我们实现好这个函数后,可以尝试以下构造一个n=5,val=1的vector,发现会报错,而构造n=5,val='a’的vector却正常,这是因为第一种构造匹配到了前面实现的以迭代器区间作为参数的构造函数,n和val的类型都是int,对于前面的函数模板InputIterator只需要进行一次类型推导,而在现在的构造函数中,int类型需要发生既需要发生类型推导,又要隐式类型转换成size_t,代价很大,所以就匹配到了以迭代器区间作为参数的构造函数。然后在该函数中对int类型解引用,所以会报非法的间接寻址。
解决方法就是再重载一个以int类型和常引用为参数的构造函数,这样当n和value都是int类型的时候,就会优先匹配到重载的函数。

vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

3.拷贝构造

3.1 传统写法

我们可以直接new一块空间,然后把值拷贝进去。
首先,我们先用memcpy尝试一下:

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

代码很简单,但是当我们使用它时,有一个问题,就是当T的类型是string时拷贝,会发生崩溃。
这是因为memcpy是按字节拷贝,也就是浅拷贝,两个vector中的string指向同一块空间,当析构时会发生崩溃,所以这种方法不太行。
还可以用for循环,一个一个拷贝:

vector(const vector<T>& v)
{
	_start = new T[v.size()];
	for (size_t i = 0; i < v.size(); ++i)
	{
		_start[i] = v._start[i];	// 此时会调用string的赋值重载,发生的是深拷贝
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

3.2 现代写法

利用构造函数创建临时变量tmp,然后将tmp和*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(const vector<T>& v)
{
	vector<T> tmp(v.begin(), v.end());
	swap(tmp);
}

4.赋值重载

赋值重载就和拷贝构造类似了,这里使用传值传参,并且不需使用临时变量tmp。

vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

5.迭代器失效问题

5.1 reserve和resize

void reserve(size_t n)  
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < size(); ++i)
			{
				tmp[i] = _start[i];		// 调用赋值重载
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

void resize(size_t n, T val = T())
{
	if (n < size())		// 缩容
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

5.2 insert

当我们使用insert时,需要传一个迭代器参数,写入时有可能会发生扩容,而根据上面的reserve,发生的是异地扩容,所以传进去的迭代器就失效了,现在其指向的是已经被释放的旧空间,如果坚持使用就会发生访问野指针。

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

void test8()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	std::vector<int>::iterator it = std::find(v.begin(), v.end(), 2);
	if (it != v.end())
	{
		v.insert(it, 10);
	}
	
	++(*it);	// 这里我们测试10是否会变成11
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述
解决方法:给insert添加一个返回值,返回新的迭代器

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
	return pos;
}

5.3 erase

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

void test8()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator pos = std::find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.erase(pos);
	}
	// erase也会引发迭代器失效,这种情况下没有事,但是当pos指向vector最后一个元素时,就会导致erase后pos指向界外,在vs中不论在哪里erase后,再修改pos都会报错
	++(*pos);
	// 解决方法:
	// if (pos != v.end())
	// {
	// 	   pos = v.erase(pos);
	// }
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

4. 整体代码(包含迭代器、析构函数等)

namespace lgr
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
		{ }

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val); 
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

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

		vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

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

		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<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}


		void reserve(size_t n)  
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * n);
					for (size_t i = 0; i < size(); ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
		
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;
			return pos;
		}

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

		void push_back(const T& val)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = val;
			++_finish;
		}
		void pop_back()
		{
			assert(!empty());
			--_finish;
		}
		const size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		const size_t size() const
		{
			return _finish - _start;
		}
		bool empty() const
		{
			return _start == _finish;
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		T& operator[](size_t n)
		{
			return *(_start + n);
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

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

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

相关文章

springboot实验室管理系统-计算机毕设 附源码86757

springboot实验室管理系统 摘 要 验室管理系统是将实验室的分析仪器通过计算机网络连起来&#xff0c;采用科学的管理思想和先进的数据库技术&#xff0c;实现以实验室为核心的整体环境的全方位管理。它集用户管理&#xff0c;实验室信息管理&#xff0c;实验室预约管理&#x…

Java设计模式——策略模式

1. 策略模式简介 策略模式: 策略模式是一种行为型模式, 它将对象和行为分开, 将行为定义为一个行为接口和具体行为的实现 策略模式最大的特点是行为的变化, 行为之间可以相互替换 每个if判断都可以理解为一个策略. 本模式是的算法可独立于使用它的用户而变化 2. 模式结构 策略…

Flink 学习七 Flink 状态(flink state)

Flink 学习七 Flink 状态(flink state) 1.状态简介 流式计算逻辑中,比如sum,max; 需要记录和后面计算使用到一些历史的累计数据, 状态就是:用户在程序逻辑中用于记录信息的变量 在Flink 中 ,状态state 不仅仅是要记录状态;在程序运行中如果失败,是需要重新恢复,所以这个状态…

Java实训第七天——2023.6.13

文章目录 一、用Visual Studio Code写一个计算器二、同一个js被多个html引用三、js操作css四、DOM对象属性的操作案例五、js解析json 一、用Visual Studio Code写一个计算器 功能&#xff1a;实现简单的加减乘除 <!DOCTYPE html> <html lang"en"> <…

LeetCode 2481. 分割圆的最少切割次数

【LetMeFly】2481.分割圆的最少切割次数 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-cuts-to-divide-a-circle/ 圆内一个 有效切割 &#xff0c;符合以下二者之一&#xff1a; 该切割是两个端点在圆上的线段&#xff0c;且该线段经过圆心。该切割是一端…

mapbox-gl 点位编辑功能

文章目录 前言方式一&#xff1a;借助 Marker添加自定义icon添加POI图层&#xff0c;绑定对应事件基于Marker交互创建自定义Marker编辑 / 创建POI 方式二&#xff1a;采用 mapbox-gl-draw 插件总结 前言 矢量在线编辑是gis常用的编辑功能&#xff0c;兴趣点&#xff08;POI&am…

kettle开发-Day38-超好用自定义数据处理组件

目录 前言&#xff1a; 一、半斤八两&#xff0c;都不太行 1、表输入&#xff0c;速度快&#xff0c;但不稳妥 2、稳的一批&#xff0c;但是慢的像蜗牛 二、各诉衷肠&#xff0c;合作共赢 1、表输入&#xff0c;高效数据插入 2、插入更新&#xff0c;一个都不能少 三、表输…

express的使用(四) nodejs转发表单到后台

原文链接 搬砖的林小白-express的使用(四) 个人博客地址&#xff0c;求关注&#xff0c;也希望大家在里面批评我的不足之处 看前提示 本篇所讲述的内容是node端转发前端发送过来的表单到第三方中&#xff0c;应用的场景有很多&#xff0c;如我们经常做的将文件存储到七牛云或…

Scala学习笔记

累了&#xff0c;基础配置不想写了&#xff0c;直接抄了→Scala的环境搭建 这里需要注意的是&#xff0c;创建新项目时&#xff0c;不要用默认的Class类&#xff0c;用Object&#xff0c;原因看→scala中的object为什么可以直接运行 一、Scala简介 1.1 图解Scala和Java的关系 1…

大数据测试基本知识

常用大数据框架结构 1.大数据测试常用到的软件工具 工具推荐&#xff0c;对于测试数据构造工具有&#xff1a;Datafaker、DbSchema、Online test data generator等&#xff1b;ETL测试工具有&#xff1a;RightData、QuerySurge等&#xff1b;数据质量检查工具&#xff1a;great…

MySQL-SQL存储过程/触发器详解(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

Three.js--》实现3d地月模型展示

目录 项目搭建 初始化three.js基础代码 创建月球模型 添加地球模型 添加模型标签 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多说直接开始。 项目搭建 本案例还…

《离散数学》:代数系统和图论导论

一、代数系统 代数系统是数学中的一个重要概念&#xff0c;它涉及一组对象以及定义在这些对象上的运算规则。代数系统可以是抽象的&#xff0c;也可以是具体的。 在抽象代数中&#xff0c;代数系统通常由一组元素和一组操作&#xff08;或称为运算&#xff09;组成。这些操作…

【MySQL新手入门系列四】:手把手教你MySQL数据查询由入门到学徒

SQL语言是与数据库交互的机制&#xff0c;是关系型数据库的标准语言。SQL语言可以用于创建、修改和查询关系数据库。SQL的SELECT语句是最重要的命令之一&#xff0c;用于从指定表中查询数据。在此博客中&#xff0c;我们将进一步了解SELECT语句以及WHERE子句以及它们的重要性。…

vue进阶-vue-route

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成&#xff0c;让用 Vue.js 构建单页应用变得轻而易举。 本章只做学习记录&#xff0c;详尽的内容一定要去官网查看api文档 Vue Router-Vue.js 的官方路由 1. 路由的基本使用 1.1 安装vue-router npm install vue-…

SpringCloud Eureka注册中心高可用集群配置(八)

当注册中心扛不住高并发的时候&#xff0c;这时候 要用集群来扛&#xff1b; 我们再新建两个module microservice-eureka-server-2002 microservice-eureka-server-2003 第一步&#xff1a; pom.xml 把依赖加下&#xff1a; <dependencies> <dependency…

golang 协程的实现原理

核心概念 要理解协程的实现, 首先需要了解go中的三个非常重要的概念, 它们分别是G, M和P, 没有看过golang源代码的可能会对它们感到陌生, 这三项是协程最主要的组成部分, 它们在golang的源代码中无处不在. G (goroutine) G是goroutine的头文字, goroutine可以解释为受管理的…

Prompt 范式产业实践分享!基于飞桨 UIE-X 和 Intel OpenVINO 实现跨模态文档信息抽取

近期 Prompt 范式备受关注&#xff0c;实际上&#xff0c;其思想在产业界已经有了一些成功的应用案例。中科院软件所和百度共同提出了大一统诸多任务的通用信息抽取技术 UIE&#xff08;Universal Information Extraction&#xff09;。截至目前&#xff0c;UIE 系列模型已发布…

Selenium 相对定位

目录 前言&#xff1a; 相对定位 工作原理 可用的相对定位 Above Below Left of Right of Near 链式相对定位 相对于WebElement的相对定位 实例演示 前言&#xff1a; Selenium传统定位基本能解决80%的定位需求&#xff0c;但是还是有一些复杂场景传统定位定不到的…

express框架学习笔记

express简介 express是一个基于Node.js平台的极简的、灵活的WEB应用开发框架。express是一个封装好的工具包&#xff0c;封装了很多功能&#xff0c;便于我们开发WEB应用&#xff08;HTTP服务&#xff09; express使用 新建express文件夹新建文件test01.js&#xff0c;代码如…