【C++】STL—— vector 模拟实现

📕 vector 简介

vector 是 C++ 的 STL 标准库中的一个容器,它主要使用的是泛型编程的思想,即使用模板。
vector 是表示可变大小数组的序列容器。它里面可以存储任意数据类型的数据,除了内置类型,还可以有自定义类型,它和数组明显的区别就是——vecctor的大小可以动态改变。当然了,不同平台下的增长方式不一样,如下图是VS 和 Linux 下的测试vector 标准库的增长方式。
请添加图片描述

📕 vector 模拟实现

框架

为了和 STL 标准库中的 vector 区分,自己实现的可以用命名空间包含起来,这里我取名为 simulate,是模拟的意思。
由于 vector 是一个容器,可以存放各种类型的数据,所以要使用模板。

namespace simulate
{
    template<class T>
	class vector
	{
	public:

	private:
		T* _start=nullptr;
		T* _finish=nullptr;
		T* _end_of_storage=nullptr;
	};
}

构造函数

如下,构造函数有多个函数重载。初始化列表可以使用缺省值代替(在申明的时候给成员变量缺省值)。

同时,这里也使用了模板,模板参数只能是迭代器区间(如果传其他的会出错),构造迭代器区间内的数据成为一个vector对象。

		// 直接用缺省值替代初始化列表
		vector(){}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				*(_start + i) = val;
				++_finish;  // 不要忘了++ _finish ,一开始忘记了,所以打印不出来
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				*(_start + i) = val;
				++_finish;  // 不要忘了++ _finish ,一开始忘记了,所以打印不出来
			}
		}


		// 写了模板,但是不加上上面的int参数重载,会报错,因为vector(10,5)会调用模板函数
		template<class InputIterator>
		vector(InputIterator begin, InputIterator end)
		{
			reserve(end - begin);
			while (begin != end)
			{
				_push_back(*begin);
				++begin;
			}
		}

但是,为什么上面有两个构造函数的非常像,区别仅仅是第一个参数的类型呢?
如下,这是使用自己定义的vector,初始化后vector里面有10个5。

simulate::vector<int> vv(10, 5);

如果没有第一个参数是 int 类型的构造函数,如下。如果是上面一行代码那样构造,传入的两个参数都被认为是 int 类型,而目前有两个参数的构造函数中,一个可以被认为是 (size_t ,int) ,另一个由于是模板,所以可以被认为是(int,int) ,编译器会选择后者
但是实际上,后者由于不传入迭代器,而是实实在在的数据,就会造成错误,因为函数体内部有解引用
所以要多一个函数重载,来重载这种情况,编译器会优先选择参数是(int,int)的,而不是使用模板。
请添加图片描述

★ 拷贝构造、赋值重载 ★

拷贝构造就涉及到深拷贝和浅拷贝的问题。
如果 vector 里面存放的数据是内置类型,根据拷贝构造方面的知识,深、浅拷贝都没有关系。
但是如果 vector 里面存放的是自定义类型,并且该类型涉及资源管理,那就必须要深拷贝。

如下图,vector 容器里面存放的是 string 类型的数据,根据对 string 类型的模拟实现,我们可以大概知道其实现原理。string 类有一个成员变量 _str 指向一串字符串。现在将 v 里面的数据拷贝到 vv 里面,如果是浅拷贝,那么就会如下图,导致 v[n] 和 vv[n] 里面的数据完全一样, _str,_size,_capcatiry ,那么 _str 指向同一块空间在析构的时候,会造成重复析构,导致崩溃

请添加图片描述

如下,深拷贝就不会出现上述问题,完全独立的空间。
请添加图片描述
那么如何实现深拷贝呢?vector是一个容器,由于使用泛型编程的思想,里面可以存放的数据类型各种各样,也许有的自定义类型,里面有多个指针分别指向不同的空间呢?我们如何知道每一次要深拷贝多少块空间呢?
所以,在这里做一个统一的标准显然是不现实的。也不能用memcpy,因为memcpy是直接把一个 vector 里面的数据拷贝到 另一个 vector,就会造成上图的浅拷贝问题。
其实,我们可以直接调用 赋值重载 ,这也是为什么这两个函数放在一起的原因。

试想一下,现在有 vector< stirng > v,里面存放了一定量的数据, vector vv(v) ,和上图一样的操作。只不过,拷贝构造内部使用的是 赋值重载 —— vv[0] = v[0] …… ,由于赋值符号两边都是string类型的数据,会调用 string 自己的赋值重载,string 内部实现的赋值重载当然是没有任何问题的!!!!其他的类型也是,内部实现的赋值重载是深拷贝,所以,这里就很好地解决了上述问题!

		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			
			for (int i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];  // 会调用自己类型的赋值重载
			}

			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

但是,如果出现了 vector< vector< string > > v ;这样的情况呢?又要拷贝构造使得vector< vector< string > > vv(v) ;容器里面存放的是容器,由于拷贝构造内部是 vv[0] = v[0] …… 赋值符号两边都是 vector< string > 类型的数据,都是 vector 类型,所以会调用 vector 自己的赋值重载如下。
还是以上述的 vector< vector< string > > v 为例, vv[0] = v[0] ,调用 vector 类型的赋值重载,即调用下面的函数。v[0] 是 vector< string > 类型的数据,该类型的数据,就像上面的两张图片一样,内部存放了一个个 string 类型的数据,如下所示。所以 _start[i] 是 string 类型的数据的指针,_start[i] = val._start[i]; 会调用 string 类型的赋值重载,那当然是没有问题的。

请添加图片描述

		// 一个 vector 赋值给另一个 vector
		vector<T> operator=(const vector<T>& val)
		{
			_start = new T[val.capacity()];
			for (int i = 0; i < val.size(); i++)
			{
				_start[i] = val._start[i];
			}

			_finish = _start + val.size();
			_end_of_storage = _start + val.capacity();

			return *this;
		}

以此类推,即使是更多层次的 vector 嵌套,也可以通过不断调用 vector 类里面的赋值重载,直到里面的类型不是 vector,而是其他的类型,而这些其他的类型是实现了深拷贝的赋值重载的,所以完全是行得通的。

resize() 、reserve()

这两个函数都和 string 类里面的两个函数类似,所以这里也不过多赘述。

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


		void reserve(int n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)  // 非空,复制数据,删除原空间。为空则不需要这样做
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}

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

_push_back() 、_pop_back()

尾插其实也和 string 类差别不大,考虑扩容。
这里也可以再深入思考一下,锻炼思维。如果参数类型 T 是 vector< string > ,那么尾插会是一次成功的吗?当然是的, *_finishi 和 x 都是T类型的数据,也就是 vector< string > ,所以会调用 vector 自己的赋值重载;然后由于其内部数据是 string 类型的,所以vector 的赋值重载内部,又会进一步调用 string 类型的赋值重载,标准库里面的string类的赋值重载,自然是没有什么问题的。

		void _push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				int sz = capacity() == 0 ? 4 : 2 * capacity();
				reserve(sz);
			}
			*_finish = x;
			++_finish;
		}

		void _pop_back()
		{
			assert(!empty());
			--_finish;
		}

★ 迭代器使用、迭代器失效问题 ★

vector 的使用,非常需要注意的一点就是迭代器失效问题。如下代码是 vector 中迭代器的设计。

		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;
		}

如下,是使用迭代器设计 insert() 函数,由于是插入,所以要考虑扩容的问题,但是如果真的扩容了,那么原本传参的 pos 迭代器指向的位置,就可能是错误的了。因为扩容后,可能开辟一块新空间,那么指向旧空间某一处的 pos 迭代器,也就失去了意义,因为旧空间在 reserve 的过程中已经被释放了。

所以,要用一个变量来存储 pos 迭代器指向的位置 和 初始位置 的长度,这里用的是 len 。下面的就是普通的后移插入过程。

		iterator insert(iterator pos , const T& x)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			size_t len = pos - _start;
			if (_finish == _end_of_storage)
			{
				reserve(2 * capacity());
				pos = _start + len;  // 扩容之后,_start 改变了
			}

			iterator tmp = _finish - 1;
			while (tmp >= pos)
			{
				*(tmp + 1) = *tmp;
				--tmp;
			}
			*pos = x;
			_finish++;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator tmp = pos + 1;

			while (tmp != _finish)
			{
				*(tmp-1) = *tmp;
				tmp++;
			}
			--_finish;

			return pos;
		}

接下来就是迭代器失效的情况,这是在使用 vector 迭代器的时候会出现的问题。
现在有一个 vector< int > v; v里面存储着一些数据,要求删除所有偶数数据。

像下面一样去写(simulate 是我用来包含自己的 vector 的命名空间,防止和标准库中的冲突),就可以了吗?可以画图尝试理解,如下图。当删除一个数据的时候,由于删除这一动作本身就会使得后面的数据前移,然后又++,就导致直接跳过了一个数据。
而如果最后一个数据是偶数,那么会删除数据并跳过 v.end() ,导致 while() 的循环条件一直满足,从而陷入死循环。

这就是典型的迭代器失效问题,原本的迭代器,在经过 插入/删除 之后,导致迭代器不是指向原本的那个数据,后续使用就可能出错。

请添加图片描述

	simulate::vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			v1.erase(it);
		}
		++it;
	}

所以,在删除数据之后,就不能再让迭代器++。如下代码,加上一个 else 就可以,这样子就可以使得删除之后不进入 else 的代码块。同时,it 也要接收 erase() 的返回值,其返回值也是 iterator ,指向被删除的后一个数据。

	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			it=v1.erase(it);
		}
		else ++it;
	}

📕 源代码


#pragma once
#include<iostream>
#include<assert.h>

namespace simulate
{
	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 (size_t i = 0; i < n; i++)
			{
				*(_start + i) = val;
				++_finish;  // 不要忘了++ _finish ,一开始忘记了,所以打印不出来
			}
		}

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


		// 写了模板,但是不加上上面的int参数重载,会报错,因为vector(10,5)会调用模板函数
		template<class InputIterator>
		vector(InputIterator begin, InputIterator end)
		{
			reserve(end - begin);
			while (begin != end)
			{
				_push_back(*begin);
				++begin;
			}
		}


		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			
			for (int i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}

			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}


		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 _end_of_storage - _start;
		}

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

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

		// 自己要写赋值重载,赋值重载和拷贝构造类似,一个 vector 复制给另一个 vector
		vector<T> operator=(const vector<T>& val)
		{
			_start = new T[val.capacity()];
			for (int i = 0; i < val.size(); i++)
			{
				_start[i] = val._start[i];
			}

			_finish = _start + val.size();
			_end_of_storage = _start + val.capacity();

			return *this;
		}


		const T& operator[](size_t x)const
		{
			assert(x < size());
			return _start[x];
		}

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

			}
		}


		void reserve(int n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)  // 非空,复制数据,删除原空间。为空则不需要这样做
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}

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

		void _push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				int sz = capacity() == 0 ? 4 : 2 * capacity();
				reserve(sz);
			}

			*_finish = x;
			++_finish;
		}

		void _pop_back()
		{
			assert(!empty());
			--_finish;
		}

		// 用迭代器,可能会出现迭代器失效的情况
		iterator insert(iterator pos , const T& x)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			size_t len = pos - _start;
			if (_finish == _end_of_storage)
			{
				reserve(2 * capacity());
				pos = _start + len;  // 扩容之后,_start 改变了
			}

			iterator tmp = _finish - 1;
			while (tmp >= pos)
			{
				*(tmp + 1) = *tmp;
				--tmp;
			}
			*pos = x;
			_finish++;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator tmp = pos + 1;

			while (tmp != _finish)
			{
				*(tmp-1) = *tmp;
				tmp++;
			}
			--_finish;

			return pos;
		}

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

	private:
		T* _start=nullptr;
		T* _finish=nullptr;
		T* _end_of_storage=nullptr;
	};
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力努力再努力.xx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值