模拟实现vector(非常详细)

模拟实现vector

1.基本概念

上一节我们讲了vector的概念以及常用的接口,这一节我们讲一下它的实现,它的底层其实就只有三个成员变量:_start,_finish,_end_of_storage。_start指向目前使用空间的头,_finish指向目前使用空间的尾,_end_of_storage指向目前可用空间的尾。通过这个三个指向就可以完成我们vector的实现,可以看一下下面的图来简单理解一下

cpp 复制代码
//基本结构
#include <iostream>
using namespace std;
namespace lnb
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;//迭代器类型,指针本身就可以做迭代器类型


	private:
		iterator _start;//指向目前使用空间的头
		iterator _finish;//指向目前使用空间的尾
		iterator _end_of_storage;//指向目前可用空间的尾
	};

}

2.vector()默认构造函数

cpp 复制代码
		vector()//默认构造函数
		:_start(nullptr),//全部设为nullptr指针
		_finish(nullptr),
		_end_of_storage(nullptr)
		{}

这是默认的构造函数,不需要传任何参数,把指针全部设为nullptr即可。后续的其他构造函数,建立在其他的成员函数上写会更方便,先继续往后看

3.size()

cpp 复制代码
		iterator begin()//迭代器的开始
		{
			return _start;
		}

		iterator end()//迭代器的结束
		{
			return _finish;
		}

		size_t size()const//vector的大小
		{
			return end() - begin();
		}

这边在实现size()的基础上顺便把迭代器的开始和结束也一起实现了,这样再写size()可以看上去更加的简便

4.capacity()

cpp 复制代码
		typedef const T* const_iterator;
		
		const_iterator begin()const//针对于const类型的对象
		{
			return _start;
		}

		const_iterator end()const//针对于const类型的对象
		{
			return _finish;
		}
		
		size_t capacity()const//vector容量大小
		{
			return _end_of_storage - begin();
		}

capacity()也是同样的道理,同时也把const迭代器给一起实现了,这边主要是capacity()被const类型修饰了,那么调用的begin()就需要也是被const修饰的,capacity()理解见下图

5.empty()

cpp 复制代码
bool empty()const//判空
		{
			return begin() == end();
		}

这个函数是用来判断vector是否为空的,我们直接使用了_start和_finish,因为当它们都为nullptr空时才会相同

6.reverse

cpp 复制代码
void reverse(size_t n)//预留空间,提前扩容用的
		{
			if (n > capacity())//保证n是大于现有空间的
			{
				size_t old_size = size();
				//1.先开辟新空间
				T* new_start = new T[n];//这边暂时不处理异常
				//2.将数据拷贝到新空间去
				for (int i = 0; i < size(); i++)
				{
					//这边不能使用memcpy这种直接内存拷贝的函数
					new_start[i] = _start[i];
				}
				//3.释放空间
				delete[] _start;
				//4.修改指向
				_start = new_start;
				_finish = new_start + old_size;
				_end_of_storage = new_start + n;
			}
		}

reverse主要是为了提前预留空间,来防止后续添加元素造成的多次扩容从而效率低下,具体的流程:先开辟新的空间→拷贝旧的数据到新空间→释放旧空间→修改指向。其中最需要注意的是拷贝元素的过程,不能直接使用内存拷贝函数,假设我们vector的元素是一个string,又或者是一个vector呢?

我们来看图理解一下,当两个vector中的string中的_str指向相同的空间时会发生什么呢?在析构的时候,old_vector底层会释放开辟的空间,而空间中的是string类型,也需要调用它的析构函数,在string的析构中会把_str指向的空间释放掉,到这也什么问题,但是当new_vector释放时呢,它也会需要释放_str指向的空间,但是已经释放过一次了呀,这时候又释放就会造成程序崩溃。所以说使用memcpy这种函数来拷贝,其实本质上也是一种浅拷贝,因此我们需要使用循环赋值才能改变这个情况,当使用循环赋值后,会自动调用string的赋值重载函数,这样str指向的空间就不是同一个了。

7.push_back()

cpp 复制代码
	void push_back(const T& val)//尾插
		{
			if (_finish==_end_of_storage)
			{
				//我们扩容以2倍增长
				reverse(capacity() == 0 ? 2 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}

我们的尾插也非常的方便,只不过是要注意一下空间够不够,不够就需要进行扩容

8.pop_back()

我们先来看一下下面的代码,尾删这样写对吗

cpp 复制代码
//!!!error code
	void pop_back()//尾删
		{
			assert(!empty());//保证有数据
			_finish--;
		}

对于内置类型确实没有问题,但是对于string这种就不行了,会造成内存泄露,因此我们需要将其进行释放

cpp 复制代码
	void pop_back()//尾删
		{
			assert(!empty());//保证有数据
			_finish->~T();//需要调用对应元素的析构,不然会造成内存泄露
			_finish--;
		}

9.operator[ ]

cpp 复制代码
		T& operator[](size_t n)//像数组一样去访问
		{
			assert(n < size());
			return _start[n];
		}

		//针对const类型的vector
		const T& operator[](size_t n)const
		{
			assert(n < size());
			return _start[n];
		}

10.resize()

cpp 复制代码
		void resize(size_t n,const T& val=T())
		{
			//要分三种情况,真正有作用的就两种情况
			if (n >size())
			{
				for (int i = 0; i < n; i++)
				{
					push_back(val);
				}
			}
			else if(n<size())
			{
				size_t distance = size()-n;//计算出多了多少元素
				for (int i = 0; i < distance; i++)
				{
					_finish->~T();//这边要记得释放,不然对于自定义类型会内存泄露
					_finish--;
				}
			}	
			else
			{
				return;
			}
		}

我们的resize要分三种情况,分别是大于等于小于n,大于n的需要减少vector的大小;小于n的需要增大vector的大小,用val来填充;而等于n什么都不用做

11.insert()

cpp 复制代码
iterator insert(iterator position, const T& val)//插入
		{
			//迭代器失效问题,因此需要保存迭代器位置
			size_t pos = position-_start;

			//1.先查看是否需要扩容
			if (_finish == _end_of_storage)
			{
				reverse(capacity() == 0 ? 2 : capacity() * 2);
				position= _start + pos;//扩容后position不在原来的空间了
			}
			//2.先进行元素的移动,将插入位置留出
			iterator end = _finish-1;
			while (end >= position)
			{
				*(end + 1) = *end;
				end--;
			}
			//3.放入元素
			*position = val;
			_finish++;
			
			return position;
		}
相关推荐
o不ok!几秒前
java中File类
java·开发语言
Zer0_on2 分钟前
C语音顺序表专题及应用
c语言·开发语言
小小小小关同学2 分钟前
【并发容器】ConcurrentLinkedQueue:优雅地实现非阻塞式线程安全队列
java·开发语言·安全
黑客Ash5 分钟前
计算机网络 | 4.网络层
开发语言·计算机网络·php
余生H5 分钟前
前端的Python入门指南(完):错误和异常处理策略及最佳实践
开发语言·前端·javascript·python
你的牧游哥12 分钟前
Mac上使用ln指令创建软链接、硬链接
开发语言·前端·javascript
Qzer_40715 分钟前
在JVM(Java虚拟机)中,PC寄存器(Program Counter Register)扮演着至关重要的角色。
java·开发语言·jvm
蜂鸟视图fengmap1 小时前
蜂鸟云平台 JavaScript SDK 与微信小程序 SDK v3.1.5 更新分析
开发语言·javascript·微信小程序·蜂鸟云平台·javascript sdk·微信小程序sdk·地图开发者
don't_be_bald1 小时前
C语言学习-循环练习
c语言·开发语言·c++·学习·算法
潜意识起点1 小时前
人工智能与Java应用场景:智能推荐系统的实现
java·开发语言·人工智能