【C++】vector的模拟实现

一、vector的迭代器

迭代器(iterator)是一个比较抽象的概念,其在STL中扮演着十分重要的角色。迭代器可以理解为一种行为类似指针的对象(可以是指针也可以不是指针),而指针的各种行为中最常见最重要的操作就是*->,因此迭代器最重要的编程工作就是operator*operator->

我们知道vector使用的是连续线性的空间,所以无论其元素类型是什么,普通指针都可以作为vector的迭代器来满足所有需求,因为vector的迭代器所需的操作,(如operator*operator->operator++operator--等),普通指针本来就具备。所以vector的迭代器就是普通指针

template<class T>
class vector
{
public:
	// vector的迭代器是一个普通指针
	typedef T* iterator;
	typedef const T* const_iterator;

由上述我们可以知道,如果用户写出这样的代码:

vector<int>:: ivit;
vector<Date>:: dvit;

ivit和dvit和类型分别是int*Date*


二、vector的数据结构

vector采用的数据结构十分简单:线性连续空间。它用两个迭代器_start_finish分别指向连续空间的已被使用范围的开始和结束,并以迭代器_endOfStorage指向整块连续空间(容量)的尾端。

为了降低扩容成本,vector实际空间大小可能比用户需求量更大一些,换句话说,一个vector的容量永远 >= 其大小。

在这里插入图片描述

增加新元素时,如果超过当前容量,则容量会扩充至两倍。至于为什么是两倍,这并不是硬性规定,只是两倍比较合适,仅此而已。

注意,上图扩容时是直接在原空间之后新增空间,实际并没有这么简单。
扩容必须经历『重新配置空间、移动元素、释放原空间』等过程,工程浩大。

使用_start_finish_endOfStorage三个迭代器便可以轻易的实现获取首尾、大小、容量、容器判空等功能。

template <class T>
class vector {
	//……
public:
	iterator begin()
	{
		return _start;
	}

	iterator end()
	{
		return _finish;//最后一个元素的下一个
	}
	//重载const版本,以适应const对象调用begin和end
	iterator begin() const
	{
		return _start;
	}

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

	const_iterator cend() const
	{
		return _finish;
	}

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

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

	bool empty() const
	{
		return _finish == _start;
	}
	void clear()
	{
		_finish = _start;
	}
	T& operator[](size_t pos)
	{
		assert(pos < size());//断言检查非法访问
		return *(_start + pos);
	}

	const T& operator[](size_t pos) const
	{
		assert(pos < size());
		//return *(_start + pos);
		return _start[pos];
	}
	//……
private:

	iterator _start; // 指向数据块的开始
	iterator _finish; // 指向有效数据的尾
	iterator _endOfStorage; // 指向存储容量的尾
};

三、vector的构造和析构

1. 构造

  1. 无参构造
vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
}
  1. n个value构造
    注意这里的n是int类型而不是size_t,假如我们这样构造:vector<int> v(3, 8),会调用区间构造而发生错误
vector(int n, const T& value = T())
{
	reserve(n);//扩容
	for (int i = 0; i < n; ++i)
	{
		_start[i] = value;
	}
}
  1. 迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

2. 拷贝构造和赋值运算符重载

vector实现swap与string实现swap的原因相同:如果用户直接使用库中swap(v1, v2)将会发生1次拷贝构造,2次赋值,3次深拷贝代价高,而使用v1.swap(v2)仅仅交换成员变量,代价小。

库中swap的实现:
在这里插入图片描述

  • 拷贝构造
void swap(vector<T>& v)
{
	//交换成员
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endOfStorage, v._endOfStorage);
}
//使用初始化列表初始化是为了
//避免交换成员后tmp的成员变量为随机值,析构时出错
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	//如果使用begin和end需要重载出const版本
	vector<T> tmp(v.cbegin(), v.cend());复用迭代器区间构造
	
	swap(tmp);
}
  • 赋值运算符重载
vector<T>& operator= (vector<T> v)//注意不要传引用
{
	swap(v);
	return *this;
}

3. 析构

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

四、vector的元素操作

1. push_back

当我们 push_back插入新元素时,函数首先检查是否还有备用空间,如果有就直接在_finish位置构造元素,并调整_finish;如果没有就扩容。

void push_back(const T& x)
{
	//容量满则扩容两倍
	if (_finish == _endOfStorage)
	{
		//注意首次扩容,容量初始化为4
		size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
		reserve(newCapacity);
	}
	*_finish = x;
	_finish++;
}

2. pop_back

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

3. insert

实现insert应注意迭代器失效问题,即扩容引起的野指针问题

// 在pos位置前面插入
iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);
	assert(pos < _finish);
	//1. 检查容量
	if (_finish == _endOfStorage)
	{
		size_t len = pos - _start;
		size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
		reserve(newCapacity);//扩容
		//扩容会导致 pos 迭代器失效,需要更新pos
		pos = _start + len;
	}

	//2. 挪动数据
	iterator end = _finish - 1;//_finish是有效元素的下一个位置
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	_finish++;
	//返回pos是为了解决迭代器失效问题
	//让用户可以在外部更新迭代器的位置
	return pos;
}

在这里插入图片描述

4. erase

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);
	iterator end = pos + 1;
	while (end != _finish)
	{
		*(end - 1) = *(end);
		++end;
	}
	_finish--;
	return pos;//解决迭代器失效问题
}

五、vector的空间操作

1. reserve的深浅拷贝问题

reserve的原型是void reserve(size_t n),我们知道当n > capacity时才需要对容器进行扩容,扩容必须经历『重新配置空间、移动元素、释放原空间』。

void reserve(size_t n)
{
	if (n > capacity())
	{
		int oldSize = size();//原size
		//1. 重新配置空间
		T* tmp = new T[n];
		//2. 移动元素
		//_start不为空才拷数据
		if (_start)
		{
			// 不能用memcpy,如果是自定义类型会发生错误
			// memcpy是浅拷贝
			//memcpy(tmp, _start, sizeof(T) * oldSize); 
			
			for (int i = 0; i < oldSize; ++i)
			{
				//调用赋值运算符重载完成深拷贝
				tmp[i] = _start[i];	
			}
			//3. 释放原空间
			delete[] _start;
		}
		_start = tmp;
		_finish = tmp + oldSize;//更新为原来对应位置
		_endOfStorage = _start + n;
	}

第一步和第三步都比较简单,而容易出现错误的地方是第二步将原空间的元素拷贝到新空间,假设模拟实现vector中的reserve接口时,使用memcpy进行拷贝,以下代码会发生什么问题?

void test()
{
	nb::vector<std::string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	v.push_back("4444");
	v.push_back("5555");
	//test结束时需要析构v
}
int main()
{
	test();
	return 0;
}

运行程序,程序会崩溃。

我们在push_back中使用reserve扩容,第一次扩容_startnullptr,我们加了if判断不会使用memcpy,就算没有if判断,拷贝0字节memcpy不会做任何事。

第一次扩容后容量被初始化为4,前4次程序不会崩溃,当第五次push_back时发生第二次扩容。

前4次插入操作后v的内存图:

在这里插入图片描述

插入"5555"期间发生第二次扩容:

1.重新配置空间

在这里插入图片描述

  1. 拷贝元素:使用memcpy拷贝
    在这里插入图片描述
  2. 释放原空间

在这里插入图片描述

这样tmp中的_str变成了野指针,其指向一块已经释放了的空间,test函数结束时v发生析构,进而string对象析构对_str进行delete,此时发生错误。解决办法是调用赋值运算符重载完成深拷贝。

总结:

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  2. 如果拷贝的是非自定义类型(int、double)的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,会引起内存泄漏甚至程序崩溃,因为memcpy的拷贝实际是浅拷贝

2. resize实现

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

整体代码

#pragma once

#include <assert.h>
namespace nb
{
	template<class T>
	class vector
	{
	public:

		// vector的迭代器是一个原生指针

		typedef T* iterator;

		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator begin() const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		iterator end() const
		{
			return _finish;
		}

		const_iterator cbegin() const
		{
			return _start;
		}

		const_iterator cend() const
		{
			return _finish;
		}

		// construct and destroy

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

		vector(int n, const T& value = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				_start[i] = value;
			}
		}

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

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			//精简复用
			vector<T> tmp(v.cbegin(), v.cend());//需要调用const版本的begin、end
			swap(tmp);
		}

		vector<T>& operator= (vector<T> v)
		{
			swap(v);
			return *this;
		}

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

		// capacity

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

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

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

		void clear()
		{
			_finish = _start;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				int oldSize = size();//原size
				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * oldSize); // --> 不能用memcpy,如果是自定义类型会发生错误
				// memcpy是浅拷贝
				//_start不为空才需要拷数据
				if (_start)
				{
					for (int i = 0; i < oldSize; ++i)
					{
						tmp[i] = _start[i];//调用赋值运算符重载完成深拷贝
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = tmp + oldSize;
				_endOfStorage = _start + n;
			}
		}

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

		///access///

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

		const T& operator[](size_t pos) const
		{
			assert(pos < _finish);
			//return *(_start + pos);
			return _start[pos];
		}

		///modify/

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

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

		void swap(vector<T>& v)
		{
			//交换成员
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
		}

		// 迭代器失效 : 扩容引起,野指针问题
		iterator insert(iterator pos, const T& val)//在pos位置前面插入
		{
			assert(pos >= _start);
			assert(pos < _finish);
			//检查容量
			if (_finish == _endOfStorage)
			{
				size_t len = pos - _start;
				size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
				reserve(newCapacity);
				//扩容会导致 pos 迭代器失效,需要更新pos
				pos = _start + len;
			}

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			_finish++;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator end = pos + 1;
			while (end != _finish)
			{
				*(end - 1) = *(end);
				++end;
			}
			_finish--;
			return pos;
		}

	private:

		iterator _start; // 指向数据块的开始

		iterator _finish; // 指向有效数据的尾

		iterator _endOfStorage; // 指向存储容量的尾
	};
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

二木 同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值