C++STL---vector模拟实现

通过上篇文章,我们知道vector的接口实际上和string是差不多的,但是他俩的内部结构却大不一样,vector内有三个成员变量:_start、_finish、_endofstorage:

_start指向容器的头元素,_finish指向有效元素末尾的元素,_endofstorage指向整个容器的尾。

而vector的迭代器就是typedef T*,也就是元素的指针。

所以我们在模拟之前就可以先做出一个小框架,为了不与库中的vector冲突,我们把他放在我们自己的命名空间中:

namespace CYF
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;//指向容器的头
		iterator _finish;//指向有效数据的尾
		iterator _endofstorage;//指向容器的尾
	};
}

构造函数

构造函数1

这个是无参构造函数,我们将他所有的成员对象都制成空指针就行:

		//构造函数1
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

构造函数2

vector还支持使用一段迭代区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说函数接收的迭代器的类型是不确定的,所以我们这里需要将构造函数设计为一个函数模板,在函数体内将该迭代区间的数据一个个尾插到容器当中即可:

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

构造函数3

vector还支持构造一种初始化有n个值为val的元素的对象。我们可以先使用reserve函数将容器的容量设为n,然后尾插n个值为val的数据到该对象即可:

		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(long n, const T& val)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n); 
	for (size_t i = 0; i < n; i++) 
	{
		push_back(val);
	}
}
vector(int n, const T& val)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n); 
	for (int i = 0; i < n; i++) 
	{
		push_back(val);
	}
}

这两个重载函数和他不同的就是第一个参数n的类型,如果没有这两个重载函数的话,我们调用下面的代码时,编译器就会优先和构造函数2匹配。

vector<int> v1(1, 3); //这里调用的是构造函数2 

并且因为构造函数2当中对两个参数进行了解引用(即将int类型解引用)报错:

补充:若我们知道需要多少空间,我们最好先使用reserve函数一次性将空间开辟好,避免调用push_back函数时再增容,导致效率降低。

拷贝构造函数

传统写法1

这里仍然需要深拷贝,先开一段与拷贝对象大小相等的空间,然后将该对象内的元素一个个拷贝过来即可,最后记得更新成员对象的值:

		拷贝构造(传统写法1)
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
			for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
			{
				_start[i] = v[i];
			}
			_finish = _satrt + v.size();//有效数据的尾
			_endofstorage = _start + v.capacity();//整个容器的尾
		}

注意我们这里数据需要一个个拷贝过来,不能使用memcpy函数。若vector内部存储的是内置类型或者无需深拷贝的自定义类型时,可以使用memcpy函数,但是当vector内存储的数据是需要深拷贝的自定义类型的时候,使用memcpy就不行了。例如:我们拿存储的数据类型是string举例:

如图:我们vector对象内元素都是string类型,每个元素都指向自己的一个字符串。若我们使用memcpy函数的话,就会出现下面的情况:

memcpy会将每个string元素内成员变量的值原封不动地拷贝过去,也就导致了两个vector对象内的元素都指向同一组string元素。最后析构环节也就会导致内存错误。

而我们拷贝时采用string类的operator=的方式,采用的是深拷贝,最终的结果是这样:

就不会导致内存问题了。

传统写法2

这里我们直接调用reserve函数进行空间开辟,而后通过范围for将原有对象一个个尾插:

		//拷贝构造(传统写法2)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}
现代写法

这里我们将v1作为临时对象,通过构造函数构造出来。而后将v1与*this互换即可,经典的现代写法,无需多言:

		//拷贝构造(现代写法)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T>v1(v.begin(),v.end());
			swap(v1);
		}

赋值运算符重载

传统写法

先判断是否为自己给自己赋值,若给自己赋值则不用做任何操作。若不是,则:先开辟一块和要拷贝对象capacity大小一样的空间,然后将数据一个个拷过来,最后更新成员对象的值:


		//赋值运算符重载函数(传统写法)
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				delete[]_start;
				_start = new T[v.capacity()];
				_finish = _start + v.size();
				_endofstorage = _satrt + v.capacity();
				for (const auto& e : v)
				{
					push_back(e);
				}
			}
            return *this;
		}

现代写法

operator=的现代写法很巧妙,右值传参的时候使用传值传参而不是引用传参,这样就直接调用了vector的拷贝构造函数,然后用这个拷贝出来的局部对象和左值进行交换,就相当于做了赋值操作,而v是局部变量,在函数结束时会自动调用析构函数销毁。

//现代写法
vector<T>& operator=(vector<T> v) 
{
	swap(v); 
	return *this; //支持连续赋值
}

析构函数

先判断是否为空指针,若不是,将空间释放,再将成员变量全都置为nullptr:

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

迭代器相关的函数(begin&&end)

我们上面讲过,vector的迭代器实际上就相当于存储数据类型的指针,begin()就是返回第一个元素地址,end()就是返回最后一个元素的下一个数据的地址:

		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对象实际上也就是使用指针遍历。并且实现了迭代器后,也就可以使用范围for,因为范围for在编译的时候,会自动替换为迭代器的形式。

关于容量和大小的函数

size && capacity

这里的size和capacity的算法跟string不同,string内直接就有两个成员变量表示size和capacity,而vector这里需要用_finish - _start和_endofstorage - _start分别得到:

		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}

reserve

 1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,不用做任何操作

我们先判断n是否大于容量,若大于,就开辟一块容量为n的空间,将原来容器中的有效数据拷贝进去,再将原来的空间释放,记得更新容器内的成员变量:

void reserve(size_t n)
{
	if (n > capacity()) 
	{
		size_t sz = size(); //记录当前容器当中有效数据的个数
		T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间
		if (_start) //判断是否为空容器
		{
			for (size_t i = 0; i < sz; i++) //拷贝数据
			{
				tmp[i] = _start[i];
			}
			delete[] _start; //释放原有空间
		}
		_start = tmp; //更新成员对象
		_finish = _start + sz; 
		_endofstorage = _start + n; 
	}
}

我们这里要注意要先记录元素的个数,否则我们最后更新_finish指针指向的时候,若调用size()函数,而size()函数是通过_finish-_start得到的,而此时_start已经指针指向已经改变,我们就会得到一个随机值,所以我们需要提前记录。

resize

 1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
 2、当n小于当前的size时,将size缩小到n。

我们需要先判断n是否小于size,若小于,将_finish改变即可,若不小于,还需要判断是否需要增容,再将扩大的数据赋值为val即可:

		void resize(size_t n, const T& val = T())
		{
			if (n < size()) //当n小于size时
			{
				_finish = _start + n; 
			}
			else 
			{
				if (n > capacity()) 
				{
					reserve(n);
				}
				while (_finish < _start + n) 
				{
					*_finish = val;
					_finish++;
				}
			}
		}

在C++中内置类型也可以看作是一个类,也有自己的默认构造函数,所以val的缺省参数可以设为T()临时对象。

empty

判断对象是否为空:

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

有关修改对象的函数

push_back

先判断是否需要增容,再尾插,最后更新_finish的指向:

		void push_back(const T& x)
		{
			if (_finish==_endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}

pop_back

判断对象是否为空,不为空将_finish--即可:

		void pop_back()
		{
			assert(!empty());//容器不能为空
			_finish--;
		}

insert

我们需要先判断pos是否合法,再判断是否需要增容,然后将pos后的元素都向后移一位,将x插在pos的位置,最后将_finish++:

		void insert(iterator pos, const T& x)
		{
			if (pos <= _finish)//判断pos是合法
			{
				size_t n = pos - _start;//记录pos和_start之间的间隔
				if (_finish == _endofstorage)
				{
					size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
					reserve(newcapacity);
				}
				pos = _start + n;//根据记录的间隔数算出pos
				iterator i = _finish;
				while (i != pos)
				{
					*(i + 1) = *i;
					i--;
				}
				*pos = x;
				_finish++;
			}
		}

提示:这里我们需要提前记录pos和_start之间的间隔,通过间隔确定增容后的pos的指向,否则pos还是指向原来已经被释放的空间。(我自己在写的时候就出现了这样的错误)

erase

先判断对象不能为空,删除时将pos后面的数据统一前移一位,覆盖掉pos的数据,返回值还为pos,即删除元素的下一个元素的位置:

		iterator erase(iterator pos)
		{
			assert(!empty());
			iterator it = pos;
			while (it != _finish)
			{
				*it = *(it+1);
				it++;
			}
			_finish--;
			return pos;
		}

swap

我们调用库中的swap函数将两个对象中的成员变量交换即可(这样就相当于交换了两个对象内部指针的指向,不耗费太多资源,若我们直接调用swap交换两个对象,若遇见例如string这样的对象,swap交换时做的全都是深拷贝操作,太耗费资源了,所以我们只交话两个对象内部的成员变量即可):

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

operator[ ]

通过下标访问vector内元素,直接返回数据即可:

		T& operator[](size_t i)
		{
			if(i<size())
			{
				return *(_start + i);
			}
		}

		const T& operator[](size_t i)const
		{
			if (i < size())
			{
				return *(_start + i);
			}
		}

最后贴上整体代码:

#pragma once
#include<iostream>
#include<cassert>

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

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

		//构造函数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);
			}
		}

		//拷贝构造(传统写法1)
		//vector(const vector<T>& v)
		//	:_start(nullptr)
		//	,_finish(nullptr)
		//	,_endofstorage(nullptr)
		//{
		//	_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
		//	for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
		//	{
		//		_start[i] = v[i];
		//	}
		//	_finish = _satrt + v.size();//有效数据的尾
		//	_endofstorage = _start + v.capacity();//整个容器的尾
		//}

		//拷贝构造(传统写法2)
		//vector(const vector<T>& v)
		//	:_start(nullptr)
		//	, _finish(nullptr)
		//	, _endofstorage(nullptr)
		//{
		//	reserve(v.capacity());
		//	for (const auto& e : v)
		//	{
		//		push_back(e);
		//	}
		//}

		//拷贝构造(现代写法)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T>v1(v.begin(),v.end());
			swap(v1);
		}

		赋值运算符重载函数(传统写法)
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)
		//	{
		//		delete[]_start;
		//		_start = new T[v.capacity()];
		//		_finish = _start + v.size();
		//		_endofstorage = _satrt + v.capacity();
		//		for (const auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}

		//赋值运算符重载函数(现代写法)
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				vector<T> v1(v.begin(),v.end());
				swap(v1);
			}
			return *this;
		}


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

		//迭代器相关函数
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}

		//容量和大小相关函数
		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		void reserve(size_t n)
		{
			if (n >= capacity())
			{
				iterator _start_ = new T[n];
				_finish = _start_ + size();
				_endofstorage = _start_ + n;
				size_t i = 0;
				while (_start_ + i != _finish)
				{
					_start_[i] = _start[i];
					i++;
				}
				delete[]_start;
				_start = _start_;
				_start_ = nullptr;
			}
		}

		//void resize(size_t n, const T& val = T())
		//{
		//	if (n <= size())
		//	{
		//		_finish = _start + n - 1;//这时候只会size,不会改变capacity
		//	}
		//	else if (n > size() && n <= capacity())
		//	{
		//		size_t more = n - size();
		//		while (more > 0)
		//		{
		//			*_finish = val;
		//			_finish++;
		//			more--;
		//		}
		//	}
		//	else
		//	{
		//		reserve(n);
		//		while (_finish != _start + n)
		//		{
		//			*_finish = val;
		//			_finish++;
		//		}
		//	}
		//}

		void resize(size_t n, const T& val = T())
		{
			if (n < size()) //当n小于当前的size时
			{
				_finish = _start + n; //将size缩小到n
			}
			else //当n大于当前的size时
			{
				if (n > capacity()) //判断是否需要增容
				{
					reserve(n);
				}
				while (_finish < _start + n) //将size扩大到n
				{
					*_finish = val;
					_finish++;
				}
			}
		}

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

		//修改容器内容相关函数
		void push_back(const T& x)
		{
			if (_finish==_endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(!empty());//容器不能为空
			_finish--;
		}

		void insert(iterator pos, const T& x)
		{
			if (pos <= _finish)//判断pos是合法
			{
				size_t n = pos - _start;
				if (_finish == _endofstorage)
				{
					size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
					reserve(newcapacity);
				}
				pos = _start + n;
				iterator i = _finish;
				while (i != pos)
				{
					*(i + 1) = *i;
					i--;
				}
				*pos = x;
				_finish++;
			}
		}

		iterator erase(iterator pos)
		{
			assert(!empty());
			iterator it = pos;
			while (it != _finish)
			{
				*it = *(it+1);
				it++;
			}
			_finish--;
			return pos;
		}

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

		//访问容器相关函数
		T& operator[](size_t i)
		{
			if(i<size())
			{
				return *(_start + i);
			}
		}

		const T& operator[](size_t i)const
		{
			if (i < size())
			{
				return *(_start + i);
			}
		}

	private:
		iterator _start;//指向容器的头
		iterator _finish;//指向有效数据的尾
		iterator _endofstorage;//指向容器的尾
	};
}

以上就是这篇文章的全部内容,谢谢大家!!

  • 32
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值