vector的模拟实现


在上一篇的博客中,我详细介绍了vector的使用,为了能够更好的使用vector,还需要仔细了解它的底层原理。
vector内部的成员有三个:

成员标志
_start表示已用空间的起始位置
_finish表示已用空间的终止位置
_end_of_storage表示可用空间的终止位置

迭代器

vector在存储方式上与数组是一样的,都是使用连续空间进行存储的,与数组不同的是它是动态的,因此vector的迭代器必须支持随机访问。正因为是连续空间的存储方式,原生指针就可以作为vector的迭代器。

//实现迭代器
		iterator begin()
		{
			return _start;
		}

		const_iterator begin() const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}


		const_iterator end() const
		{
			return _finish;
		}

构造函数

构造函数有很多种:

无参构造函数

无参构造,说明vector中所有的指针都是空指针,vector内部没有分配空间。那么可以通过初始化vector中的三个成员为空指针来进行构造。

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

带长度和初始化值的构造函数

在创建时在vector放入n个初始化值,也就是在开始的时候对vector的空间和内容进行初始化。

	vector(int n, const T& val = T())//这里的第二个值为初始化值,缺省值相当于一个匿名对象,也就是你是什么类型,就初始化什么
			: _start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);
			//将要初始化的值放进去
			//可以通过指针来实现,也可以通过push_back实现
			while (n--)
			{
				//push_back(val);
				*(_start + n) = val;
				_finish++;
			}
		}

在实现的时候为了防止代码冗余,在修改空间的时候复用了reserve函数(reserve函数的实现在下面介绍),对内容的初始化遍历赋值或者复用push_back函数。

使用迭代器的构造函数

由于vector是通过连续空间进行存储的,那么它的迭代器可以用原生指针来代替,具有随机访问的功能。那么通过迭代器来进行初始化就是遍历这一段空间,将该段空间具有的值放入。

		//3、通过迭代器初始化
		//使用函数模板
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			reserve(last - first);
			while (first != last)
			{
				//push_back(*first);
				first++;
			}
		}

使用函数模板的原因是,迭代器的种类也有很多,那么如果只写一个单一的函数,遇到其他的迭代器还得重新再写一个,这个就可以通过函数模板解决。

拷贝构造函数

拷贝构造函数就是对于给定的一个vector,构造一个与之相同的vector。为了减少代码的冗余,尽量使用已经写好的函数复用。那么我们的思路可以是这样的:要拷贝构造一个与给定的vector,首先内部空间一定是相同的,那么可以用reserve函数开辟与给定的vector相同的空间;然后对内部的元素进行遍历拷贝。

//拷贝构造函数
//下面这种开辟空间以及成员变量的修改操作是在reserve函数中进行的
//这块存在一点问题:正常来说,如果拷贝构造不进行初始化的话,在reserve时不是空指针就会对空间进行释放,此时就会报错
		vector(const vector<T>& v)
			: _start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(v.capacity());
			//此处同理也不能用memcpy进行拷贝

			//如果命名为cbegin,cend就会出错
			for (auto e : v)
				push_back(e);
		}

这里面有几处细节需要注意:
1、在用reserve函数进行拷贝构造的时候,需要对成员变量进行空指针初始化,这是由于调用的是reserve函数,在开始开辟空间的时候会对原空间进行释放,此时如果没有初始化就是野指针,那么此时释放就会出错。
2、这里对于元素的拷贝不能用memcpy,因为memcpy拷贝的时候进行的是浅拷贝,在vector析构的时候,就会出现析构两次的情况。

当然这里也可以不用reserve函数,自己来开辟空间进行拷贝构造。

		vector(const vector<T>& v)
			//: _start(nullptr)
			//, _finish(nullptr)
			//, _endOfStorage(nullptr)
		{
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); ++i)
				_start[i] = v._start[i];
			_finish = _start + v.size();
			_endOfStorage = _start + v.capacity();
		}

赋值运算符重载

赋值运算符的重载多采用现代写法


void swap(vector<T>& v)
{
	//用域限定符首先查找全局的
	::swap(_start, v._start);
	::swap(_finish, v._finish);
	::swap(_endOfStorage, v._endOfStorage);
}
		
//如果是用现代写法的话,在交换之后v就会销毁,如果不初始化为空指针,就会销毁随机空间,这是错误的
//用交换函数这里不能带引用,否则就会改变原来的值
vector<T>& operator=(vector<T> v)//之所以可以交换,是因为在传参的时候进行了深拷贝,交换之后v就会销毁,刚好将原来的空间释放
{
	swap(v);//这里的交换函数最好自己实现一下,如果用库函数给的,就需要多次进行深拷贝
	return *this;
}

现代写法的赋值运算符重载是通过交换两个不同的vector来完成的。为了不改变原vector的值,在传参的时候没有加引用,此时编译器会拷贝构造一份,我们将该vector与当前的进行交换。交换之后v会自动销毁,完成拷贝构造的目的。

注意:
1、由于现代写法的拷贝构造需要用到拷贝构造,并且交换之后会进行释放,那么这里就需要将拷贝构造进行初始化。
2、这里的交换函数最好自己实现一下,如果用库函数给的,就需要多次进行深拷贝。

reserve函数和resize函数

reserve函数是用来改变vector的空间容量的。reserve函数分为两个方面,如果改变的空间容量小于当前容量,reserve函数是什么都不做的,也不会去改变空间容量;如果改变的空间容量大于当前容量,reserve会将vector的空间改为想要开辟的容量。
vector开空间的时候是需要付出代价的,也就是重新配置,移动数据,释放原空间三步。

//reserve函数
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();//要将原本size的长度保存下来,否则后面扩容之后就会改变指针
		//delete[] _start;//销毁_start也就意味着将其他的指针也销毁了
		iterator tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T)*sz);
			//memcpy拷贝是浅拷贝,也就是直接将源文件的空间给到目标文件的空间
			//如果是内置类型或适合浅拷贝类型,这样做是没问题的
			//如果涉及到资源管理,那么就会出现释放两次的情况
			for (size_t i = 0; i < sz; ++i)
				tmp[i] = _start[i];//可以直接赋值,这样对于一些深拷贝的类型,赋值重载是深拷贝,完全没有问题

			delete[] _start;//销毁_start也就意味着将其他的指针也销毁了

		}
		_start = tmp;
		_finish = _start + sz;
		_endOfStorage = _start + n;
	}
}

resize函数是用来改变当前有效空间的大小,并且可以为新的空间赋值。
resize实现时分为三类:
第一类是改变的长度小于当前有效长度,那么此时只需要将_finsh的位置前移即可。
第二类是改变的长度大于有效长度,但是小于空间容量。此时只需要将新增的长度赋值即可。
第三类是改变的长度大于空间容量,那么就需要先开辟新的空间,然后在新增的长度中赋值。

可以发现,第一类就是将有效长度减去即可。第二类和第三类空间的增长与reserve函数相同,那么可以调用reserve函数,只需要对新的长度赋值即可。

//resize函数
void resize(size_t n, T val = T())
{
	if (n > size())
	{
		reserve(n);
		int len = n - size();
		while (len--)
		{
			*_finish = val;
			_finish++;
		}
	}
	else
	{
		_finish -= (size() - n);
	}
}

插入函数

尾插

vector的尾部插入就是在_finish的位置插入一个元素,这里只需要考虑的是空间满了的扩容情况。

//尾插
void push_back(const T& val)
{
	//如果空间够了就扩容
	if (_finish == _endOfStorage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = val;
	++_finish;
}

insert函数

insert函数是在任意位置进行插入,由于vector是连续的空间,因此在任意位置插入是需要进行数据的挪动的,代价比较大。

单个元素的插入

//1、单个元素的插入
iterator insert(iterator pos, const T& val)
{
	assert(pos);
	//如果空间不够就扩容
	if (_finish == _endOfStorage)
	{
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	iterator ptr = _finish;
	//挪动数据
	while (ptr != pos)
	{
		*ptr = *(ptr - 1);
		--ptr;
	}
	*pos = val;
	_finish++;
	return pos + 1;
}

插入多个元素

//2、插入多个元素
void insert(iterator pos, int n, const T& val)
{
	assert(pos);
	//扩容之后空间就变了,因此需要记录下原来元素的坐标
	size_t old_sz = pos - _start;
	iterator new_ptr = _finish + n;//得到插入多个元素之后的长度
	//大于容量则需要扩容
	if (new_ptr > _endOfStorage)
		reserve(new_ptr - _start);
	pos = _start + old_sz;
	iterator ptr = pos;
	//挪动数据
	while (ptr != _finish)
	{
		*(ptr + n) = *ptr;
		++ptr;
	}
	_finish += n; 
	//从pos位置开始放入多个元素
	while (n--)
	{
		*pos = val;
		pos++;
	}
}

插入一段区间

//3、在某个位置插入区间
template<class InputIterator>
void insert(iterator pos, InputIterator first, InputIterator last)
{
	assert(pos);
	//首先算出要插入的长度
	int len = last - first;
	size_t old_sz = pos - _start;
	iterator new_ptr = _finish + len;//得到插入多个元素之后的长度
	//大于容量则需要扩容
	if (new_ptr > _endOfStorage)
		reserve(new_ptr - _start);
	pos = _start + old_sz;
	iterator ptr = pos;
	//挪动数据
	while (ptr != _finish)
	{
		*(ptr + len) = *ptr;
		++ptr;
	}
	_finish += len;
	while (first != last)
	{
		*pos = *first;
		pos++;
		first++;
	}
}

删除函数

尾删

//尾删
void pop_back()
{
	//这里要断言一下,如果为空则不能尾删
	assert(!empty());
	_finish--;
}

erase函数

删除单个元素

//1、删除单个元素
		iterator erase(iterator pos)
		{
			assert(pos);
			iterator ptr = pos + 1;
			while (ptr != _finish)
			{
				*(ptr - 1) = *ptr;
				ptr++;
			}
			_finish--;
			return pos;
		}

删除一段元素

//2、删除一段元素
void erase(iterator first, iterator last)
{
	int len = last - first;
	iterator ptr = last + 1;
  			while (ptr != _finish)
	{
		*(ptr - len) = *ptr;
		ptr++;
	}

	_finish -= len;
}

重载[]运算符

重载[]运算符能够使得vector像数组一样去访问元素

//重载[]运算符
T& operator[](size_t n)
{
	return *(_start + n);
}

const T& operator[](size_t n) const
{
	return *(_start + n);
}

其他函数

size()函数

//size函数
size_t size() const
{
	return _finish - _start;
}

capacity()函数

//capacity函数
size_t capacity() const
{
	return _endOfStorage - _start;
}

析构函数

//析构函数
~vector()
{
	if (_start)
		delete[] _start;//销毁_start相当于将整个vector销毁
	_start = _finish = _endOfStorage = nullptr;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值