深入篇【C++】手搓模拟实现vector类(详细剖析接口底层实现原理):【200行代码实现】

深入篇【C++】手搓模拟实现vector类(详细剖析接口底层实现原理)【200行代码实现】

  • 【vector类模拟实现代码】
  • Ⅰ.构造/析构
      • 1.vector()
      • 2.operator=
      • 3.~string()
  • Ⅱ.访问遍历
      • 1.operator[]
      • 2.begin()/end()
  • Ⅲ.增操作
      • 1.push_back()
      • 2.insert()
  • Ⅳ.删操作
      • 1.erase()
      • 2.pop_back()
  • Ⅴ.查操作
      • 1.size()
      • 2.capacity()
  • Ⅵ.改操作
      • 1.reserve()
      • 2.resize()

【vector类模拟实现代码】

#pragma once

#include 
//vector的模拟实现
#include 
#include 
#include 
#include 
using namespace std;
namespace tao
{
	template <class T>//定义一个模板T
	class vector
	{
		
	public:
		typedef T* iterator;//将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;
		}
		//成员函数
		vector()//空值构造
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{}
		vector(const vector<T>& v)//深拷贝
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start+v.size();
			_endstroage = _start+v.capacity();
		}
		void swap(vector<T> v)
		{
			std::swap(_start , v._start);
			std::swap(_finish ,v._finish);
			std::swap(_endstroage ,v._endstroage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endstroage = nullptr;
			}
		}
		void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* temp = new T[n];//首先开空间
				if (_start != nullptr)
				{
					//将数据拷贝到temp去
					memcpy(temp, _start, sizeof(T) * sz);
					//删除原来空间
					delete[] _start;
				}
				//最后将空间赋值给_start
				_start = temp;
				_finish = _start + sz;
				//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
				//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
				_endstroage = _start + n;
			}
		}
		void push_back(const T& val)
		{
			//首先要考虑是否扩容
			if (_finish == _endstroage)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
			}
			*_finish = val;
			++_finish;

		 }
		size_t capacity() const
		{
			return _endstroage - _start;
		}
		size_t size() const
		{
			return _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];
		}
		iterator insert(iterator pos,const T &val)
		{
			assert(pos >= _start && pos <= _finish);
			//首先考虑扩容----这里有一个问题:迭代器失效
			//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
			//需要将将pos迭代器恢复,需要更新pos的新位置。
			if (_finish == _endstroage)
			{
				size_t len = pos - _start;
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
				pos = _start + len;
			}
			//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;
			//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
			return pos;
			//指向新插入位置的迭代器
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
			//返回的是删除元素的下一个位置的迭代器
		}
		void pop_back()
		{
			erase(--end());
		}
		void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
				//填值
				while (_finish != n + _start)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
	private:
       //成员变量
		iterator _start;//指向开头位置的迭代器
		iterator _finish;//指向真实数据的最后位置
		iterator _endstroage;//指向容量的的最后位置

	};

}

这里vector的实现需要用到模板,因为vector就是用模板实例化出各种类型的vector。
深入篇【C++】手搓模拟实现vector类(详细剖析接口底层实现原理):【200行代码实现】_第1张图片
根据源码,它是按照上图的方式来进行处理的,成员变量是三个迭代器,start迭代器指向开头位置,finish迭代器指向最后一位有效数据的后面,endstroage迭代器指向的是有效容量的后面位置。
而这里的
size=_finish-start;
capcaity=endstorage-start;
迭代器是一种新的类型,需要自己定义,我们这里用typedef在类里定义迭代器的
typedef T* iterator;将T* 重命名为iterator

Ⅰ.构造/析构

1.vector()

1.vector最常用的构造就是无参构造了。这里只需要将vector的成员变量都初始化成空就可以了。

vector()//空值构造
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{}

拷贝构造对于自定义类型需要使用深度拷贝,不能浅拷贝。
这里我们跟string类的拷贝构造类似,首先要初始化,给对象开空间,然后将值拷贝过去。对应的成员变量要一致。

vector(const vector<T>& v)//深拷贝
			: _start(nullptr)
			, _finish(nullptr)
			, _endstroage(nullptr)
		{
			_start = new T[v.size()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start+v.size();
			_endstroage = _start+v.capacity();
		}

2.operator=

①赋值运算符重载比如v1=v;将v赋值给v1,这里的v1和v都是存在的对象。可以用类似于string类里的赋值重载。首先用tmp开空间,将值拷贝到tmp中去,释放原来的空间,最后再将temp赋值给对象。
②不过我们可以用一个更简单的方式,我们知道对象v传过来函数用形参接收,我们想要就是这个形参的空间大小和数据,然后将原有的v1空间释放,将形参的对象的空间赋给v。我们这里可以直接使用swap函数让v1和v直接交换。这样v1就获得v的空间大小和数据了。最后交换完后的v1就变成形参了,函数结束后v1就会被销毁,即原来的空间被释放。

void swap(vector<T> v)//将v和原对象数据和空间交换
		{
			std::swap(_start , v._start);
			std::swap(_finish ,v._finish);
			std::swap(_endstroage ,v._endstroage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			//交换完后,原对象的空间会因为变成形参后函数结束自动销毁。
			return *this;
		}

3.~string()

判断一下statr是否为空指针,如果为空指针那就不用释放了,如果不为空指针说明还有数据。需要释放,new[] 与delete[]配合使用,释放完后,将指针都置空。防止变成野指针。

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

Ⅱ.访问遍历

1.operator[]

使用下标和方括号来访问变量和遍历。首先我们需要知道对象的大小。而获取大小即finish-start即可。这里我们封装成函数size().
获取pos位置上的数据,而数据是在指针指向的地方,也就是_start[pos]。

     	T& operator[](size_t pos)
		{
			assert(pos <= size());//断言判断一下pos位置是否合法
			return _start[pos];
		}
		const T& operator[](size_t pos) const//用于const修饰的对象访问和遍历。
		{
			assert(pos <= size());
			return _start[pos];
		}

2.begin()/end()

利用迭代器访问和遍历对象也是很常见的,迭代器我们知道是用typedef T* iterator定义的,将T* 重命名为iterator,其本质上可以看成不同类型的指针。
begin()就是返回指向开头位置的迭代器
end()就是返回指向最后一个数据的后面的迭代器。
迭代器有很多类型,还有const修饰的迭代器,即const T类型的 利用typedef const T const_iterator.将const T*重命名为const_iterator.

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

Ⅲ.增操作

①尾插一个数据,首先需要判断是否要扩容,可以直接使用reserve()函数扩容,请上改操作里查看reserve()的实现。这里直接使用。这里需要讨论一下,容量是否为0,如果为0那么直接给它开辟4个空间,如果不是0那么就按照两倍的扩容。
②如果不需要扩容直接在finish位置插入数据,记得插入完,finish需要往后挪动一下。

1.push_back()

void push_back(const T& val)
		{
			//首先要考虑是否扩容
			if (_finish == _endstroage)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
			}
			*_finish = val;
			++_finish;

		 }

2.insert()

①相比较string类的insert这里vector就不太一样了,因为string中的insert使用的还是下标,而这里使用的是迭代器。在pos位置插入val。
①首先需要判断pos位置的合法性
②再判断是否需要扩容
③将数据挪动,从最后一个数据开始挪动,给pos位置留出位置。
④将数据插入pos位置,finish需要往后挪动一下。
⑤最后将pos返回也就是返回新插入数据的位置。
要考虑到如果扩容了,那肯定是异地扩容,那原来pos指向的原空间会因为扩容后,原空间释放,pos就变成野指针了。即pos迭代器失效了,不能再访问pos位置上的数据了,这里的解决方法是要扩容后,要更新pos位置。
使用迭代器的好处就是不用考虑头插时下标要小于0的问题了(string类里遇到的问题).因为迭代器是一个地址,不可以为0的。

iterator insert(iterator pos,const T &val)
		{
			assert(pos >= _start && pos <= _finish);
			//首先考虑扩容----这里有一个问题:迭代器失效
			//当迭代器扩容时,这里的pos迭代器就相当于失效了,因为原来的空间被释放了,pos也就变成野指针了。
			//需要将将pos迭代器恢复,需要更新pos的新位置。
			if (_finish == _endstroage)
			{
				size_t len = pos - _start;//记录pos位置在哪
				size_t newcapacity = (capacity() == 0 ? 4 : capacity() * 2);
				reserve(newcapacity);
				pos = _start + len;//扩容完更新pos位置,防止变成野指针。
			}
			//使用迭代器的好处就是可以避免string那样头插时,挪动数据,下标要小于0的问题,因为迭代器是一个地址,不可以为0的
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;
			//insert 中的扩容迭代器失效,外部迭代器的解决方法是使用返回值,将pos位置返回过去,再用迭代器接收,就可以对pos位置上的内容再访问了
			return pos;
			//指向新插入位置的迭代器
		}

Ⅳ.删操作

1.erase()

①相比较string类里的erase这里vector的erase也不一样了,用的是迭代器作为位置,而不是下标。
②首先判断pos位置是否合法
③挪动数据,将pos位置覆盖。从前往后挪动。
④挪动完后,将finish往前挪动。
⑤返回pos,这里返回的是被删除数据的下一个元素的位置。

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

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
			//返回的是删除元素的下一个位置的迭代器
		}

2.pop_back()

尾删,可以直接复用erase,删除位置也就是end()前面的位置。因为end()指向的是最后一个数据的后面位置。

void pop_back()
		{
			erase(--end());
		}

Ⅴ.查操作

vector类里没有直接查找的函数比如find,不过在算法里有find,使用迭代器就可以用。
①size()数据的大小,其实就是finish-start。

1.size()

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

capacity()容量的大小,其实就是endstorage-statr。

2.capacity()

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

Ⅵ.改操作

1.reserve()

①扩容逻辑其实很简单,首先要判断要开辟的空间是否比原空间要大。如果小的话就不用开了。
②首先利用temp保存n大小空间
③然后将原对象数据拷贝到tmp去(如果原对象是空的,那就不用拷贝了直接将空间赋给对象即可)
④拷贝完将原空间释放。
⑤最后将tmp空间和数据赋给_start。_finish位置要更新。
这里要注意finish位置如何更新呢?很多人会这样写:_finish=_start+size();注意这样是不可以的。为什么呢?
因为在更新finish之前,statrt的位置已经改变了,不再指向原来的位置,而finish还是指向原来的空间位置。而size()=finish-start.最终计算出来finish就等于空了。问题就出在start位置已经改变了。所以size()里计算的就不是数据的大小了。我们应该先保存一份start没有改变的数据,一开始就记录一个数据大小,最后再加上就可以了。

void reserve(size_t n)
		{
			size_t sz = size();
			if (n > capacity())
			{
				T* temp = new T[n];//首先开空间
				if (_start != nullptr)
				{
					//将数据拷贝到temp去
					memcpy(temp, _start, sizeof(T) * sz);
					//删除原来空间
					delete[] _start;
				}
				//最后将空间赋值给_start
				_start = temp;
				_finish = _start + sz;
				//这里有一个问题,size()的计算是用_finish -start 而这里的start已经改变,而finish还没有改变
				//最后计算finish就变成空了,最终的问题在于start改变了,所有在之前要保留一份size()的数据
				_endstroage = _start + n;
			}
		}

2.resize()

①resize()是vector最常用的几个接口,可以给对象纪创建空间又可以初始化,一般来说初始化的值是缺省值,不给定的时就使用缺省值初始化,给定值时就用这个值初始化。通常默认是用0初始化,但是这里不可以用0初始化,因为不一定为vector类型,还可能是其他类型。所以这里给的是T()。
②T() 本质是一个匿名对象,会自动调用默认构造。对于自定义类型,就会调用默认构造。但对于内置类型呢?好像内置类型没有构造函数吧? 因为有了模板,内置类型升级了,也有了类似构造函数,就比如int i=int()。这里默认int()是0,而int j=int(1),这里给j初始化的就是1了。
③resize()可以分成三种情况,第一种ncapacity,这种情况就需要扩容了,然后将多余的初始化。
④不过这里可以将二三情况合并,不管需不需扩容,都使用reserve扩容到n,因为reserve会自动检查是否需要扩容。
⑤最后就需要填值了,从finish开始填值到n+_start位置。

void resize(size_t n, const T& val=T())//这里的val可以给缺省值的,当给定时,使用给定值,不给定时使用缺省值,缺省值给的是T类型的构造函数
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);//不用管原来的容量多少,reserve会判断是否需要扩容
				//填值
				while (_finish != n + _start)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

你可能感兴趣的:(C++,c++,算法,前端)