STL中vector的模拟实现

本文介绍了C++STL中的vector容器,包括其成员变量、构造函数(无参构造、析构、拷贝构造、迭代器区间构造)、接口实现如reserve、resize、push_back、迭代器begin和end、重载[]操作符等,并讨论了拷贝构造和赋值重载的现代写法,以及insert和erase方法。文章还提到了对存储自定义类型数据时浅拷贝的问题及解决方案,并通过杨辉三角的例子展示了优化后的代码。
摘要由CSDN通过智能技术生成

目录

STL

vector

1.vector的成员变量

​编辑

2.vector的基础接口实现

<1>无参构造函数

<2>析构函数

<3>reserve和resize

<4>push_back

<*>迭代器的begin和end接口

<5>重载[]

<6>拷贝构造函数(传统写法)

<7>迭代器区间构造函数(重点)

<8>赋值重载

<*>重载拷贝构造和赋值函数(现代写法)

<9>insert

<10>erase

<*> 扩展与优化(重点)


STL

简介:

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构,以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

而我们,则应该学会如何使用它,理解它,再到实现它。

vector

之前,我们进行了对string的模拟实现,string的模拟实现---by 风君子吖,对STL有了初步的了解,而vector则与string有很强的相似性,他们都是存储数据类,所以,如果你已经了解了string,相信使用vector也是易如反掌。

简介:

vector是表示可变大小数组的序列容器,而区别它与数组的地方,则是比普通数组管理更为方便,并且对自定义类型有了更好的使用管理。

vector是C++库非常重要的组成部分,其C++库对其的实现相对复杂,如果需要细致了解,应查询相应文档->https://legacy.cplusplus.com/reference/vector/vector/?kw=vector,而我们为了初步了解,则尽量简化它的实现。

1.vector的成员变量

首先,vector是一个模板类,所以它不像string只能存char类型的数据,他可以存储内置类型的数据和自定义类型的数据,这是与string第一个不同的地方。

vector的成员变量是三个迭代器,用三个迭代器来代替了string中的size和capacity,这就是与string第二个不同的地方。并且,由于vector和string一样,它是一种以顺序表存储数据的结构,所以,它的迭代器其实就是原生指针。

我们先看看vector的源代码

我们可以从源代码看出,vector的成员变量只有三种->strat 、finish、end_of_storage。

 源代码十分复杂,如果要全部看懂需要较大的学习成本,对于初学者不太建议细攻源代码

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

我们从上图可以看到,start指向的是数据的起始位置,finish指向的是最后一个有效数据的后一个位置,而end_of_storage则是指向的此数组的最后的空间的后一个位置。

利用这三个迭代器,我们就可以说实现像stirng中一样的size 和 capacity.

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

2.vector的基础接口实现

<1>无参构造函数

我们先来实现一个无参的构造函数

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

这种无参的构造函数,目前看来并没有什么意义,所以需要实现更多的接口来扩展它,暂时先这样。

<2>析构函数

~vector()
{
	delete[] _start;
}

析构函数很简单,只需要调用delete释放空间即可。

<3>reserve和resize

void reserve(size_t n)
{
	if (_end_of_storage - _start < n)   //判断是否需要扩容
	{
		size_t sz = size();   //保存size的数据
		T* tmp = new T[n];
		if (_start)           //如果_start不为空指针,则进行拷贝和delete
		{
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

reserve作用:reserve 是用来给数组进行扩容时调用的函数,当插入数据时,如果空间不够,则调用它进行扩容。

 注意:这种写法有隐患,如果存储的是一个二维数组,则会出现浅拷贝,我们后面再对reserve进行优化。

void resize(size_t n, T val = T())  //T()是调用匿名对象的构造函数
{
	if (capacity() < n)
	{
		reserve(n);
	}
	if (size() < n)
	{
		while (_finish < _start + n)
		{
			*_finish = val;
			finish++;
		}
	}
	if (size() > n)
	{
		_finish = _start + n;
	}
}

resize作用: resize 是用来修改该数组的有效存储数据个数的。

<4>push_back

作用:尾插

bool empty() const
{
	if (_end_of_storage - _start)
	{
		return false;
	}
	return true;
}
void push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(empty() ? 4 : capacity() * 2);  //如果数据为空,则给初始空间为4
	}
	*_finish = val;
	finish++;
}

尾插的实现很简单,先检查容量是否已满,已满则调用reserve进行扩容。 

<*>迭代器的begin和end接口

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

作用:实现了begin和end接口后,我们就可以使用范围for来进行对vector的遍历。

void vector_test1()
{
    vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(4);
	v1.push_back(3);
	v1.push_back(5);
	v1.push_back(6);
	for (auto& i : v1)
	{
		std::cout << i << " ";
	}
}

<5>重载[]

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

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

作用:重载[]后,访问数据会很方便。

注意:最好用assert断言一下pos,防止非法访问导致程序崩溃。

<6>拷贝构造函数(传统写法)

//传统写法
vector(const vector<T>& v)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	if (!v.empty())  //如果v为空,则不处理
	{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, sizeof(T) * v.size());
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
	}
}

如果看过我模拟实现string的文章,就知道这种写法叫做现代写法,通过一个tmp去调用构造函数来

<7>迭代器区间构造函数(重点)

文档中还有一种构造函数,称之为迭代器区间构造函数

 它通过将迭代器的一个区间来实现构造,将区间内的数据作为初始化数据。

     //迭代器区间构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

而它最大的特点:他是一个模版,这意味着你不止可以传T的类型,还可以传其他类型,例如,T为int,你传一个char迭代器区间,他也是可以支持的。

 而它输出的结果为什么是104,101,108,108,111,细心的朋友会发现这些是它的ascll码值。

先解引用再创建一个临时变量进行隐私类型转换成int类型,就变成了ascll码值。

<8>赋值重载

vector<T>& operator=(const vector<T>& v)   //赋值重载
{
	if (!v.empty())  //如果v为空,则不处理
	{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, sizeof(T) * v.size());
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
	}
	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(const vector<T>& v)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	vector<T> tmp(v.begin(),v.end());
	Swap(tmp);
}
vector<T>& operator=(vector<T> v)   //赋值重载
{
	Swap(v);
	return *this;
}

现代写法简洁,所以比较提倡这种写法。

<9>insert

const iterator& insert(iterator pos, const T& val)
{
	assert(pos <= _finish);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;   //记录pos的相对位置,防止迭代器失效。
		reserve(empty() ? 4 : capacity() * 2);  //如果数据为空,则给初始空间为4
		pos = _start + len;          //定位新pos位置
	}
	for (iterator end = _finish - 1; end >= pos; --end)
	{
		end[1] = end[0];
	}
	*pos = val;
	_finish += 1;
	return pos;
}

void insert(iterator pos, size_t n, const T& val)
{
	assert(pos <= _finish);
	if (size()+n > capacity())
	{
		size_t len = pos - _start; //记录pos的相对位置,防止迭代器失效。
		reserve(size() + n);
     	pos = _start + len;        //定位新pos位置
	}
	for (iterator end = _finish - 1; end >= pos; --end)
	{
		end[n] = end[0];
	}
	for (int i = 0; i < n; i++)
	{
		pos[i] = val;
	}
	_finish += n;
}

作用:在pos位置插入一个/一串数据

注意:要小心迭代器因为扩容调用reserve而失效。

<10>erase

iterator erase(iterator pos)
{
	assert(pos < _finish);
	for (int i = 0 ; i < _finish - pos -1 ; i++)
	{
		pos[i] = pos[i+1];
	}
	--_finish;
	return pos;
}

iterator erase(iterator first, iterator last)
{
	assert(last <= _finish);
	assert(first <= _finish);
	size_t len = last - first;
	for (int i = 0; i < _finish - last; i++)
	{
		first[i] = last[i];
	}
	_finish -= len;
	return first;
}

注意:最好用assert断言一下pos,防止非法访问导致程序崩溃。

<*> 扩展与优化(重点)

以杨辉三角为例,如果要求你写一个以vector<int>为模版的vector<vector<int>>的二维数组,我们上面所写的代码是否会有问题?

答案是有的,如果存储的是一个指针数组,我们的代码会有几个浅拷贝问题。

reserve拷贝的时候,赋值拷贝的时候,拷贝构造的时候。

应修改成

        //传统写法
vector(const vector<T>& v)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	if (!v.empty())  //如果v为空,则不处理
	{
	_start = new T[v.capacity()];
	//memcpy(_start, v._start, sizeof(T) * v.size());  //memcpy也是浅拷贝
	for (int i = 0; i < size(); i++)
	{
		_start[i] = v._statr[i]   //调用赋值来完成深拷贝
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
	}
}

vector<T>& operator=(const vector<T>& v)   //赋值重载
{
	if (!v.empty())  //如果v为空,则不处理
	{
	_start = new T[v.capacity()];
	//memcpy(_start, v._start, sizeof(T) * v.size());  //memcpy也是浅拷贝
	for (int i = 0; i < size(); i++)
	{
		_start[i] = v._statr[i]   //调用赋值来完成深拷贝
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
	}
	return *this;
}

void reserve(size_t n)
{
	if (capacity() < n)   //判断是否需要扩容
	{
		size_t sz = size();   //保存size的数据
		T* tmp = new T[n];
		if (_start)           //如果_start不为空指针,则进行拷贝和delete
		{
			//memcpy(tmp, _start, sizeof(T)* size());   //memcpy也是浅拷贝
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];   //调用赋值来完成深拷贝
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

让我们来实现杨辉三角来验证我们上面的修改是否正确

class Solution {
	public:
		vector<vector<int>> generate(int numRows) {
			vector<vector<int>> vv;
			vv.resize(numRows);
			for (int i = 0; i < vv.size(); i++)
			{
				vv[i].resize(i + 1, 0);
				vv[i].front() = vv[i].back() = 1;
			}
			for (int i = 0; i < vv.size(); i++)
			{
				for (int k = 0; k < vv[i].size(); k++)
				{
					if (vv[i][k] == 0)
					{
						vv[i][k] = vv[i - 1][k] + vv[i - 1][k - 1];
					}
				}
			}
			return vv;
		}
	};
	void vector_test5()
	{
		vector<vector<int>> vv = Solution().generate(5);
		for (int i = 0; i < vv.size(); i++)
		{
			for (int k = 0; k < vv[i].size(); k++)
			{
				cout << vv[i][k] << " ";
			}
			cout << endl;
		}
	}

结果正确! 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风君子吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值