【C++】vector模拟实现

简介:

本文将利用C/C++模拟vector常用接口实现,可以更好的帮助理解vector底层。

主体的实现思路是先搭起来一个能跑的架子,再一点点的补充完善。

私有成员:

与我们曾经实现过得顺序表有些不一样,顺序表是一个malloc出来的指针指向数组,另外还有capacity与size

但在这里我们参照vector的原码进行模拟,使用三个迭代器进行控制这个动态数组。

	private:
		iterator start = nullptr;
		iterator finish = nullptr;
		iterator end_of_storage = nullptr;

故因此我们先要了解一下这个迭代器的设计,我们在模拟string时,由于string底层也是数组,由于虚拟地址的存在,我们认为数组在内存是连续的,故可以直接使用原生指针进行模拟迭代器,vector也是数组,我们当然也可以使用原生指针进行模拟迭代器

在这里插入图片描述
关于各个指针的含义

迭代器:

由于我们的vector需要支持不同类型,需要使用模版进行设计,故我们设置迭代器时如下图定义即可。

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

至于为什么设计两个类型迭代器是为了const对象也可以调用,更具体可以看一下string模拟实现中的迭代器。

无参构造函数:

无参的没什么好说的,直接全初始化为nullptr。

		vector()
			:start(nullptr)
			, finish(nullptr)
			, end_of_storage(nullptr)
		{}

push_back:

reserve:

因为push_back等很多操作涉及扩容的问题,故我们可以先实现一个reserve的接口,方便接下来使用:

		void reserve(size_t n)
		{
			size_t capacity = end_of_storage - start;
			if (capacity < n)
			{
				size_t size = finish - start;
				T* tmp = new T[n];
				//memcpy(tmp, start, size * sizeof(T));
				for (int i = 0; i < size; i++)
				{
					*(tmp + i) = *(start + i);
				}
				delete[] start;

				start = tmp;
				finish = tmp + size;
				end_of_storage = tmp + n;
			}
		}

关于为什么使用逐个赋值而不是直接memcpy是为了防止浅拷贝,在接下来会有演示

既然都实现了reserve那么resize也就顺便实现一下。

resize:

		void resize(size_t n, const T& val = T())
		{
			size_t capacity = end_of_storage - start;
			if (n > capacity)
			{
				reserve(n);
				size_t len = end_of_storage - finish;
				while (len--)
				{
					*finish = val;
					finish++;
				}
			}
			else
			{
				finish = start + n;
			}
		}

push_back:

		void push_back(const T& val)
		{
			if (finish == end_of_storage)
			{
				size_t capacity = end_of_storage - start;

				reserve(capacity == 0 ? 4 : capacity * 2);
			}
			*finish = val;
			finish++;
		}

这里针对嵌套类型也不用担心浅拷贝。
如果嵌套string类
*finish是一个string对象,赋值时会调用string内部的深拷贝。

operator[]重载:

		T& operator[](size_t pos)
		{
			return start[pos];
		}

		const T& operator[](size_t pos) const
		{
			return start[pos];
		}

由于const对象也会调用,故我们也要实现const版本的重载。

begin && end:

		iterator begin()
		{
			return start;
		}

		iterator end()
		{
			return finish;
		}

		const_iterator end() const
		{
			return finish;
		}

		const_iterator begin() const
		{
			return start;
		}

size && capacity:

		size_t size()
		{
			return finish - start;
		}

		size_t capacity()
		{
			return end_of_storage - start;
		}

		size_t size() const
		{
			return finish - start;
		}

		size_t capacity() const
		{
			return end_of_storage - start;
		}

insert:

		void insert(iterator pos, const T& val)
		{
			if (finish == end_of_storage)
			{
				int distance = pos - start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = start + distance;
			}

			int i = 0;
			int count = finish - pos;
			while(count--)
			{
				*(finish - i) = *(finish - i - 1);
				i++;
			}
			finish++;
			*pos = val;
		}

erase:

		void erase(iterator pos)
		{
			assert(size());
			int count = finish - pos - 1;
			int i = 0;
			while (count--)
			{
				*(pos + i) = *(pos + i + 1);
				i++;
			}
			finish--;
		}

带参构造:

		vector(int n, const T& val = T())
		{
			reserve(n);

			while (n--)
			{
				push_back(val);
			}
		}

迭代器区间构造:

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

拷贝构造 && 赋值运算符重载:

关于拷贝构造与赋值运算符重载一般有两种实现方案,一般方法与现代方法
下图是现代方法,龙我们已经写好的接口进行拷贝,不用自己开空间,自己赋值等操作。

		vector(const vector<T>& v)
		{
			for (auto& val : v)
			{
				push_back(val);
			}
		}

而赋值的做法更绝,因为传参不带引用会调用拷贝构造。我们直接利用拷贝构造的成果,进行swap,而等赋值拷贝结束会自动调用析构,帮助我们将旧的vector对象进行解决。

		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& operator= (vector<T> val)
		{
			swap(val);

			return *this;
		}

析构函数:

		~vector()
		{
			delete[] start;
			start = finish = end_of_storage = nullptr;
		}

深浅拷贝的验证:

当我们在vector内放置自定义类型,如果你的程序浅拷贝,那么大概率会报错
在这里插入图片描述
而深浅拷贝都会发生在扩容,我们的扩容依赖的都是reserve这个接口,故这个接口一定不能发生浅拷贝的问题。

就像我们在reserve哪里说的,不能使用memcpy,这样会发生浅拷贝
在这里插入图片描述
解决方法也很简单,利用自定义类型的深拷贝即可

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值