C++模拟底层实现vector

C++模拟底层实现vector

1.引言

​ 1.本篇将使用命名空间Arthur来模拟实现一下STL库中的容器vector。包括但不仅限于主要功能:push_back()、popback()、resize()、reserve()、erase()等函数。 以上函数都会包含在vector.h头文件中。

​ 2.首先将会介绍基本的框架,再按照各个函数的具体功能、介绍以及每个模块会涉及的问题做好分类目录以供查阅。

​ 3.STL库中具体vector实现的源码以及一些判异常的逻辑较为复杂,本篇只将其中精华的思想以及经典遇到问题的思想呈现出来,具体细节还烦请转阅STL3.0源码中的具体实现!

在这里插入图片描述

2.具体实现

2.1 基本框架

#include<assert.h>
namespace Arthur{

	template<class T>		//采用模板以便于任意类型的使用
	class vector{
		public:
				typedef T* iterator;			//将任意类型T 起别名为iterator以供后续方便使用
				typedef const T* const_iterator;  //const对象调用就用const_iterator
				
				vector(){}
				
				// ...  具体全部函数实现在vector的public中
				
	private:
    	iterator _start = nullptr;				//定义三个指针分别是表示一段数据的开头 一段数据的结尾 和一段数据的容量
    	iterator _finish = nullptr;
    	iterattor _end_of_storage =nullptr;
	
	};



}

1.首先把vector函数写在自己Arthur命名空间内,以防跟库中的冲突。

2.类中把任意类型和const对象的任意类型分别命名,以供普通对象和const对象分别使用。

3.在私有成员项给上_start _finish和 _end _ of _storage三个迭代器表示一段数据的开头,结尾和容量三个数据,并且给上缺省的nullptr以表初始值。


2.2 构造析构函数

vector()		//普通构造 上面已经给过缺省值其实没必要再给
: _start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}


vector(const vector<T>& v){  //拷贝构造函数,注意不能使用memcpy等函数实现浅拷贝,而要实现深拷贝,单独开辟空间
	_start = new T[v.capacity()];
	for(size_t i = 0;i<v.size();i++)//循环一个个给到新的对象去
		_start[i] = v._start[i];
		
		_finish = _start+v.size();	//给完后更新_finish和_end_of_storage
		_end_of_storage = _start +v.capacity();

}

//初始化构造,实现功能如 :vector(10,5)会初始化成10个5
vector(size_t n,const T& val= T())  //这里的T()采用调用默认构造生成匿名对象的方法,节省代码量
{
 	reserve(n);
    for(size_t i =0;i<n;i++)
        push_back(val);
}

vector(int n,const T& val= T())	//写个一样的把size_t 改成int类型方便后面int使用不报错
{
 	reserve(n);
    for(int i =0;i<n;i++)
        push_back(val);
}

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


1.这里实现拷贝构造的时候不能使用浅拷贝,否则新旧对象会占用同一块内存单元会导致报错,且不符合拷贝构造本意。

2.实现初始化构造的时候T()是调用的默认构造生成匿名对象的方法,匿名对象的生命周期原本只在改行,可是这里相当于给了匿名对象一个val别名,生命周期就随着val的结束而结束了。


2.3 各类迭代器size()&capacity()

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

const_iterator begin() const {		//const版本,方便const对象
		return _start;
}

const_iterator end() const{
		return _finish;
}


size_t size(){
		reutrn _finish-_start;
}

size_t capacity(){
		return _end_of_storage - _start;
}

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



template<class InputIterator>  //这里的InputIterator是让vector适配各类的迭代器  如vector<string>时候能够运行
vector(InputIterator first, InputIterator last)
{
    while(first != last){
        push_back(*first);
        ++first;
    }
}

1.这里的InputIterator是为了让各类迭代器都能够适配vector,因为各个类型的迭代器都支持迭代器解引用,迭代器++等操作,所以采用了一个模板范式的一个操作。


2.4 运算符重载[]

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

}

1。这里在重写了[]运算符后返回的时候只需判断一下位置的合法性就直接可以采用[]来返回结果了。

2.5 reserve()&resize()

void reserve(size_t n){ 
	if(n>capacity()){
	size_t sz = size(); //这里定义一个sz保存内存变换之前的长度,后面要用
		T* temp  = new T[n];
		if(_start){
				for(size_t i = 0;i<sz;i++)
				temp[i] = _start[i];
				delete[] _start;  //全部换过来之后把原空间释放掉
		}
	}
	_start = temp;		//更新各个指针
	_finish  =_start +sz;
	_end_of_storage = _start+n;

}




void resize(size_t n,const T& val  = T()) //默认构造生成匿名对象
{	
if(n<capacity()){
	_finish = _start+n;
}
else{
		if(n>capacity()){
		reserve(n);
		while(_finish !=_start+n)
		{
			_finish = val;
			++_finish;
		}
	}
}



}

1.这里在reserve的时候不要调用例如memset所造成的浅拷贝问题。

2.resize的容量小于自身的容量就相当于缩容,直接改_finish指针即可。

3。n>resize就把尾部给上n个val即可。


2.6 push_back()&pop_back()

void push_back(const T& x) {
			if (_finish == _end_of_storage)     //检查容量够不够
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);//capacity为0的话就给4
			}
			*_finish = x;
			++_finish;
		}
		
		void pop_back() {
			assert(!empty());
			_finish--;
		}

  1. push_back 直接把尾部给上x,pop_back 直接将_finish–即可。

2.7 insert()&erase()

void insert(iterator pos, const T& val) {        //把pos到end的位置往后挪 再在pos位置插入val
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)     //检查容量够不够
			{
				size_t len = pos - _start; //更新pos 采用相对位置的方法,防止扩容内存更新后pos未更新问题。解决迭代器失效问题
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len; //				如果这里只插入四个值再调用insert 就会发生迭代器失效问题 
			}						//		因为到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!

			iterator end = _finish - 1;
			while (end >= pos) {
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;

		}			//insert以后 我们认为pos失效(不能用pos来*(pos)++ 等操作)




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

			iterator start = pos + 1;
			while (start != _finish) {		//在要删除的位置后一个挪到前一个上,删除之后将size--
				*(start - 1) = *start;
				++start;
			}
			--_finish;
			return pos;

		}//erase之后我们认为迭代器仍然失效 比如利用迭代器删除偶数个元素时,vs下迭代器会报错  但是比如Linux下不会报错
			//为了解决这个问题 我们让返回值变成一个迭代器类型 返回一个新的内存地址的pos

1.这里的插入存在迭代器失效的隐患:容量到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!

为了解决这个问题我们需要更新pos位置,方法是采用记住相对位置的距离len,重新开辟空间后再加上len来更新pos即可。

2.在这里我们认为在insert和erase之后迭代器失效了,例如insert后不能用迭代器实现*(pos)++功能,erase后不能利用迭代器删除偶数个元素的功能。

为了解决这些矛盾,我们让返回值变成一个迭代器,返回一个新内存地址的pos即可。

3.全部代码实现

#pragma once
#include<assert.h>
namespace Arthur {
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()				//构造
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		vector(const vector<T>& v) {
			//	reserve(v.capacity());
			//	memcpy(_start, v._start, sizeof(T) * v.size());//memcpy是浅拷贝 按字节拷贝 不i会开辟新空间  memcpy来copy内置类型不会出问题
			//	_finish = _start + v.size();			//但是自定义和string类等就不能用memcpy


			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++) {	//新开空间每次循环赋值 就是深拷贝
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}



		vector(size_t n, const T& val = T())//这里 不能给0 因为T可能是任意类型,给零不合适,所以这里给默认构造生成匿名对象的方法
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{	//一般匿名对象生命周期只在这一行 因为在这行后没人会用了	//但是val是匿名对象的别名了 生命周期就延长了 
			reserve(n);
			for (size_t i = 0; i < n; i++)
				push_back(val);


		}

		vector(int n, const T& val = T())//这里 不能给0 因为T可能是任意类型,给零不合适,所以这里给默认构造生成匿名对象的方法
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{	//一般匿名对象生命周期只在这一行 因为在这行后没人会用了	//但是val是匿名对象的别名了 生命周期就延长了 
			reserve(n);
			for (int i = 0; i < n; i++)
				push_back(val);
		}





		template <class InputIterator> //这里的InputIterator 代表的就是vector适配任意类型的迭代器
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{//所有迭代器区间一定是左闭右开[first,last)

			while (first != last) {
				push_back(*first);
				++first;
			}
		}




		~vector() {
			delete[] _start;
			_finish = _end_of_storage = nullptr;
		}
		size_t size() const {	//size()函数
			return _finish - _start;
		}
		size_t capacity() const {		//capacity()函数
			return _end_of_storage - _start;
		}


		iterator begin() {				//迭代器begin()
			return _start;
		}

		iterator end() {				//迭代器end()			
			return _finish;
		}

		const_iterator begin() const {				//迭代器begin()
			return _start;
		}

		const_iterator end() const {				//迭代器end()			
			return _finish;
		}

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


		void reserve(size_t n) {//reserve函数

			if (n > capacity()) {
				size_t sz = size();
				T* temp = new T[n];
				if (_start) {
					//memcpy(temp, _start, sizeof(T)*size());
					for (size_t i = 0; i < sz; i++) {	//不用memcpy以防造成深拷贝问题
						temp[i] = _start[i];
					}
					delete[] _start;   //delete后面加[]代表删除的是一个数组
				}


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


		}


		void resize(size_t n, T val = T()) //默认构造
		{
			if (n < capacity()) {
				_finish = _start + n;//相当于删除
			}
			else {
				if (n > capacity()) {
					reserve(n);

					while (_finish != _start + n) {
						_finish = val;
						_finish++;
					}
				}
			}
		}


		void insert(iterator pos, const T& val) {        //把pos到end的位置往后挪 再在pos位置插入val
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)     //检查容量够不够
			{
				size_t len = pos - _start; //更新pos 采用相对位置的方法,防止扩容内存更新后pos未更新问题。解决迭代器失效问题
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len; //				如果这里只插入四个值再调用insert 就会发生迭代器失效问题 
			}						//		因为到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!

			iterator end = _finish - 1;
			while (end >= pos) {
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;

		}			//insert以后 我们认为pos失效(不能用pos来*(pos)++ 等操作)


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

			iterator start = pos + 1;
			while (start != _finish) {
				*(start - 1) = *start;
				++start;
			}
			--_finish;
			return pos;

		}//erase之后我们认为迭代器仍然失效 比如利用迭代器删除偶数个元素时,vs下迭代器会报错  但是比如Linux下不会报错
			//为了解决这个问题 我们让返回值变成一个迭代器类型 返回一个新的内存地址的pos


		void push_back(const T& x) {
			if (_finish == _end_of_storage)     //检查容量够不够
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);//capacity为0的话就给4
			}
			*_finish = x;
			++_finish;
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());

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

			//return _start + pos;
			return _start[pos];
		}

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







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

4.总结

我们在vector的实现里面解决了几个经典的问题: 迭代器失效问题、深浅拷贝问题。具体通过这次模拟实现我们更清楚的知道了vector具体底层的实现逻辑究竟是什么。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Arthur___Cui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值