模拟实现C++中vector常用方法

前言

要模拟实现前:我们最主要是熟悉使用vector容器先,基本的使用我在好早一篇文章有介绍vector的使用,有需要可以看看c++基础 STL 第一篇: (初识STL 和 vector 容器)


vector的成员数据

对于一个vector来说:底层就是一个数组来实现;我们用模板来封装它,其实我们就是模板STL里的vector写的,
这里的iterator就是一个指针,只不过我们给他重命名而已,这也是模仿STL里面的写法的;

template<class T>
class vector
{
public:
	typedef T* iterator;//可读可写迭代器
	typedef const T* const_iterator; //只读迭代器
private:
	iterator _start; //表示指向数组的头指针
	iterator _finish;//表示指向数组的最后一个元素的下一个位置
	iterator _end_of_stroage;//表示数组的容量
};

vector的默认构造函数

对于构造函数的调用有很多方式:
这里先实现默认构造:

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

vector的拷贝构造和赋值运算

对于拷贝构造和赋值运算符都要完成深拷贝
逻辑也很简单:
先开辟一个新空间,把旧空间数据拷贝过去,就可以;

vector(const vector<T>& v)
{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, sizeof(T).v.size()); //注意这里也会引发浅拷贝的问题
	
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

但是这种写法,没有以下的写法好:

//通过迭代器区间的构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}
//现代写法的拷贝构造
vector(const vector<T>& v)
{
	vector<T> temp(v.begin(), v.end());
	std::swap(_start, temp._start);
	std::swap(_finish, temp._finish);
	std::swap(_end_of_storage, temp._end_of_storage);
}

上面第一个函数模板:是利用区间来构造出vector的构造函数,用迭代器区间初始化;
一旦有了这个构造函数,我们就可以在拷贝构造函数里:先复用该迭代器区间的的构造函数,构造一个临时对象,
再去交换this和临时对象的数据,即可完成拷贝;


对于赋值运算符,也可以这么做,甚至我们在传参时候,不传引用,直接传值,这样就可以在参数直接构造出对象了:

vector<T>& operator(const vector<T> v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
	return *this;
}

vector的迭代器

vector的迭代器种类也很多:
这里就只实现最常用的两种迭代器:


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

只读迭代器和可读可写迭代器:都是返回起始位置和最后一个位置的下一个位置的迭代器;


vector的[ ] 和 size 和 capacity


size_t size() const
{
	//迭代器相减就是它们的大小
	 return _finish - _start;
}
size_t capacity() const
{
	//迭代器相减就是它们的容量
	return _end_of_storage - _start;
}
T& operator[] (size_t pos)
{
	//断言防止越界访问
	assert(pos < size());
	//return _start[pos]; //同下
	return *(_start + pos);
}

vector的resize和 reserve

这两个都是扩容函数:只不过resize扩容并初始化,而reserve不会;
对于reserve我们只需要考虑,插入的数据大于容量就扩容即可:

 void reserve(size_t n) //扩容
{
	if (n > capacity())
	{
	 //扩容				 
		size_t sz = size(); //提前保存size()大小
		T* temp = new T[n];
		if (_start) //旧空间不为空
		{
		 //拷贝旧数据到新空间
		 memcpy(temp, _start, sizeof(T)*size());
		//释放旧数据空间
		delete[] _start;
		}
	_start = temp;//再让_start指向temp
	_finish = _start + sz;
	_end_of_storage = _start + n;
	}			 
}

这里要注意的一个问题就是:扩容前需要提前保存旧指针的size大小,为了是扩容过后,_finish能够正确的指向需要的位置;


假如没有提前保存size大小的分析:
在这里插入图片描述


上面的扩容还是会出大问题:其中出问题就是在于memcpy,因为memcpy是字节拷贝,也就是浅拷贝,很容易引发析构两次的问题:
所以我们要解决这个问题:

 void reserve(size_t n) //扩容
{
	if (n > capacity())
	{
	 //扩容				 
		size_t sz = size(); //提前保存size()大小
		T* temp = new T[n];
		if (_start) //旧空间不为空
		{
		 //拷贝旧数据到新空间
		for(size_t i = 0; i< sz;i++){
			temp[i] = _start[i]; //这里调用赋值运算符,完成深拷贝
		}
		//释放旧数据空间
		delete[] _start;
		}
	_start = temp;//再让_start指向temp
	_finish = _start + sz;
	_end_of_storage = _start + n;
	}			 
}

对于resize函数依旧很简单:

 void resize(size_t n, const T& = T())
{	//如果扩容的大小小于原始数据大小
	if (n <size())
	{
		 _finish = _start + n;
	}
	else//如果扩容的大小大于原始数据大小
	{
		if (n > capacity())
	{
		reserve(n);
	}
		//往里添数据
		while (_finish != _start+n)
		{
	 		*_finish = val;
	 		++_finish;
		 }
 	} 
}

vector的push_back和pop_back

这个逻辑很简单的:需要注意的是,插入数据四皇后,判断不要超过容量;
删除数据时候,不要超过起始位置即可;

void push_back(const T& x)
{
	if (_finish >= _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x; //尾插数据
	_finish++;
}
void pop_back()
{
	assert(_finish > _start);
	--_finish;
}

vector的insert和erase(会产生迭代器失效问题)

iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == capacity())
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	//插入逻辑
	iterator end = _finish - 1;

	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = v;
	_finish++;
	return pos;
}	

这样的插入逻辑会导致迭代器失效,是pos的迭代器失效了(pos是指向原来空间的位置)
因为当插入数据在容量扩容时候,我们的this对象就指向其他的新空间了
而原来的pos迭代器还是指向旧的空间,此时再扩容的情况下,我们还是用
pos位置的迭代器,就会发生越界(发生在while条件判断里),因为旧空间在扩容时候就失效了;
在这里插入图片描述


//解决pos迭代器失效的办法:就是在扩容之前保存pos位置的距离
//同时扩容成功后,更新pos的位置
//但是这个函数,在使用者调用时候,本身就会导致失效问题:只要发生扩容,调用者拿到insert返回值就会失效;

iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == capacity()){
		size_t len = pos - _start; //提前保存pos位置的距离
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len; //扩容后,更新pos的位置
	}				
	//插入逻辑
	iterator end = _finish - 1;

	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = v;
	_finish++;
	return pos;
}

删除:
对于删除逻辑也很简单

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

erase函数的使用也会导致迭代器失效的问题:
当调用这使用该函数时候,传入的迭代器实参意义会发生变化;
它不再指向被删除的元素了,而是指向被删除元素下一个位置的那个元素;


对于vector可能会导致其迭代器失效的操作有:
会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、
push_back等。


vector 类的全部代码

#pragma once

namespace xjh
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	public:
		iterator begin()	{ return _start; }
		iterator end()		{ return _finish; }
		const_iterator begin() const { return _start; }
		const_iterator end() const { return _start; }
		vector()
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{}

		//通过迭代器区间的构造函数
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		//现代写法的拷贝构造
		vector(const vector<T>& v)
		{
			vector<T> temp(v.begin(), v.end());

			std::swap(_start, temp._start);
			std::swap(_finish, temp._finish);
			std::swap(_end_of_storage, temp._end_of_storage);

		}
		//现代写法的赋值运算符
		vector<T>& operator(const vector<T> v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
			return *this;
		}
		T& operator[] (size_t pos)
		{
			assert(pos < size());
			//return _start[pos]; //同下
			return *(_start + pos);
		}
		 size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		 size_t size() const
		 {
			 return _finish - _start;
		 }
		 void resize(size_t n, const T& = T())
		 {
			 if (n <size())
			 {
				 _finish = _start + n;
			 }
			 else
			 {
				 if (n > capacity())
				 {
					 reserve(n);
				 }
				 //往里添数据
				 while (_finish != _start+n)
				 {
					 *_finish = val;
					 ++_finish;
				 }
			 }
		 }
		 void reserve(size_t n) //扩容
		 {
			 if (n > capacity())
			 {
				 //扩容				 
				 size_t sz = size(); //提前保存size()大小
				 T* temp = new T[n];
				 if (_start) //旧空间不为空
				 {
					 拷贝旧数据到新空间
					 //这种方式会引发浅拷贝的问题
					 //memcpy(temp, _start, sizeof(T)*size());
					 
					 //这里调用赋值运算符重载,深拷贝
					 for (szie_t i = 0; i > sz; i++){
						 temp[i] = _start[i];
					 }
					 //释放旧数据空间
					 delete[] _start;
				 }
				 _start = temp;//再让_start指向temp
				 _finish = _start + sz;
				 _end_of_storage = _start + n;
			 }			 
		 }
		void push_back(const T& x)
		{
			if (_finish >= _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x; //尾插数据
			_finish++;
		}
		void pop_back()
		{
			assert(_finish > _start);
			--_finish;
		}
		iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == capacity()){
				size_t len = pos - _start; //提前保存pos位置的距离
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len; //扩容后,更新pos的位置
			}				
			//插入逻辑
			iterator end = _finish - 1;

			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = v;
			_finish++;
			return pos;
		}
		//erase函数的使用也会导致迭代器失效的问题:
		//当调用这使用该函数时候,传入的迭代器实参意义会发生变化
		//它不再指向被删除的元素了,而是指向被删除元素下一个位置的那个元素
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}
			--_finish;
			return pos;
		}
	};
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呋喃吖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值