C++STL详解(四):vector的模拟实现

vector各函数的接口
namespace mlf
{
	//模拟实现vector
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认成员函数
		vector();                                           //构造函数
		vector(size_t n, const T& val);                     //构造函数
		template<class InputIterator>                      
		vector(InputIterator first, InputIterator last);    //构造函数
		vector(const vector<T>& v);                         //拷贝构造函数
		vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数
		~vector();                                          //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size()const;
		size_t capacity()const;
		void reserve(size_t n);
		void resize(size_t n, const T& val = T());
		bool empty()const;

		//修改容器内容相关函数
		void push_back(const T& x);
		void pop_back();
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(vector<T>& v);

		//访问容器相关函数
		T& operator[](size_t i);
		const T& operator[](size_t i)const;

	private:
		iterator _start;        //指向vector的起始位置
		iterator _finish;       //指向vector中最后一个有效数据的下一个位置
		iterator _endofstorage; //指向vector中最后一个空间的下一个位置
	};
}

vector的成员变量

vector里面有三个成员变量,分别是:_start、_finish与_endofstorage。

在这里插入图片描述

通过上面的这幅图可以帮助我们更好地去理解他们。

默认成员函数

在上一篇文章我们说过:vector是支持四种方式去初始化的,其中有三种初始化方式都是使用我们的构造函数初始化,另外一种则是使用拷贝构造初始化,这个放到后面说。接下来我们就来说一下这三个构造函数吧。

构造函数一

首先vector的第一种初始化方式是使用无参的构造函数初始化,这个实现起来比较简单,我们只需要将我们的成员变量都初始化成nullptr即可

        //构造函数一
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}
构造函数二

vector还可以使用给定n个val值的方式去初始化,我们可以先调用reserve函数提前开足够的空间,然后再将val值插入的vector中,这样做的好处可以使得我们插入val的时候不用再去考虑增容的问题了。

        //构造函数二
		vector(size_t  n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			//提前开好空间
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
构造函数三

其次我们的vector还支持使用一段迭代器区间去初始化,因为这个迭代器可以是其他任何容器的迭代器,这也就说明了我们接收到的迭代器类型是不确定的,因此我们需要将这个构造函数设计成函数模板,然后再将该迭代器区间里面的内容插入到vector中即可。

注意:类模板的成员函数,还可以再是函数模板。

        //构造函数三
		// 类模板的成员函数,还可以再是函数模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
拷贝构造

vector的拷贝构造设计深拷贝问题,这里提供两种深拷贝的写法:

写法一:现代写法

现代写法比较简单,先使用reserve函数开辟足够的空间(这样做的好处使得我们后面尾插的时候不必再去考虑增容的问题),再使用范围for(或者其他遍历方式)对容器v进行遍历,在遍历过程中将容器v中的数据一个个尾插过来。

            //现代写法
            vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
            {
            //实现方法一
			//使用方法一的时候一定要将指针初始化
			//否则reserve的时候会出问题
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
            }

注意:使用这种写法的时候一定要先将三个成员变量初始化成nullptr,否则调用reserve函数的时候就会出问题。

写法二:传统写法

拷贝构造的传统写法大家应该很容易就能想到,先开辟一块与当前对象一样大的空间,然后再将当前对象里面的数据一个个拷贝到新空间里面即可,最后更新_finish与endofstorage的指向即可。

            //传统写法
            //v1(v3)
            vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			//实现方法二
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++)
			{
				//调用赋值运算符重载
				_start[i] = v._start[i];
			}
			//更新指针
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();

		}

注意:将当前对象里面的数据一个个拷贝过来的时候不能够使用memcpy函数,当vector存储的数据时内置类型或者浅拷贝的自定义类型(Date)时,使用memcpy函数是没什么问题的,但是当vector存储的数据是需要进行深拷贝的自定义类型时,我们就不能够使用memcpy函数去拷贝了,这里涉及到了更深层次的深浅拷贝问题。

例如:当vector存储的数据时string类的时候。

在这里插入图片描述

并且vector当中存储的每一个string都指向自己所存储的字符串

在这里插入图片描述

如果我们当前使用的是memcpy函数来拷贝旧空间数据的话,由于memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。那么拷贝构造出来的vector所存储的string成员变量将会与被拷贝的vector当中所存储的成员变量指向同一块空间

在这里插入图片描述

这样的话就得不到我们想要的结果了,那么我们代码里面是怎么解决这个问题的呢?

            for (size_t i = 0; i < v.size(); i++)
			{
				//调用赋值运算符重载
				_start[i] = v._start[i];
			}

这段代码看似是使用"="将容器中的数据一个个拷贝过来,实际上是调用了所存储类型的赋值运算符重载函数,而我们string类的赋值运算符重载函数正好就是深拷贝,从而就达到了我们想要的结果。

在这里插入图片描述

总结:如果我们vector中所存储的元素类型是内置类型(int)或者是浅拷贝的自定义类型(Date),那么可以使用memcpy函数进行拷贝,但是如果是需要深拷贝的自定义类型(string),那么就一定不能够使用memcpy函数去拷贝。

赋值运算符重载

赋值运算符重载同样要实现深拷贝,我们这里也提供两种深拷贝的写法:

写法一:

对于写法一,我们需要先判断是否为自己给自己赋值,若是自己给自己赋值则不进行操作直接返回*this,如果不是自己给自己赋值,我们先释放旧空间,再开辟一块与v一样大的空间,再将v中的数据一个个给拷贝过来,更新指针的指向即可。

        //赋值运算符重载
		//v1 = v3
		//实现方法一
		vector<T>& operator=(const vector<T>& v)
		{
			//防止自己给自己赋值
			if (this != &v)
			{
				delete[]_start;
				_start = new T[v.capacity()];
				for (size_t i = 0; i < v.size(); i++)//将容器v当中的数据一个个拷过来
				{
					_start[i] = v._start[i];
				}
				_finish = _start + v.size();
				_endofstorage = _start + v.capacity();
			}
			//支持连续赋值
			return *this;
		}

注意: 这里和拷贝构造函数的传统写法类似,我们同样也不能够使用memcpy函数进行拷贝

写法二:

赋值运算符重载的第二种深拷贝写法非常的简洁,但是尽管简洁却包含了很多的细节,我希望大家能够理解它。

我们这里的v并不是引用传参,而是传值传参,所以v就是外面v3的拷贝构造,里面v的修改并不会影响外面的v3,这是一个小细节。

其次我们只需要交换this与v里面的数据,最终再返回*this就能达到想要的结果了。

这里还是用string里面点外卖的那个例子说一下:我们想吃东西,但是又不想自己做,那么我就点外卖让骑手 (相当于这里的v) 给我送过来(相当于这里的swap(v)),早上妈妈出去的时候,叫我下楼的时候记得把垃圾给丢下去,可是我上午出去的时候忘记丢了,可是这个时候我又不想丢垃圾那我就让骑手帮我丢 (我自己的这块空间不想要了,那我将里面的数据和v里面的数据交换之后,等函数调用结束时,v会自动调用它的析构函数,从而就帮助我们处理掉了这块空间), 不帮我丢的话就给差评(典型不当人行为hhhh)。

        //赋值运算符重载
		//实现方法二
		//v1 = v3
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

注: swap函数的实现在后面。

析构函数

vector中的析构函数实现起来比较简单,首先判断一下_start指针是否为nullptr,只要不为空指针,就释放它所指向的空间,然后再将三个成员变量都置成nullptr即可。

        //析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
			}
			_start = _finish = _endofstorage = nullptr;
		}
与迭代器相关的函数

与string的实现类似,我们vector迭代器的底层也是由指针去实现的,只不过不一样的是由于vector能存储各种容器的数据,因此我们这里的vector实现成了类模板。而vector中的迭代器实际上就是当前vector所存储的数据类型的指针。

        typedef T* iterator;
		typedef const T* const_iterator;

我们在上篇文章介绍以及使用vector的时候,发现我们既可以通过迭代器去访问遍历vector里面的内容,同时我们也可以修改vector里面的内容。因此对于迭代器我们就需要实现两个版本

版本一:只支持遍历访问vector里面的内容,不能够去修改vector里面的内容,并且我们不需要修改成员变量,我们最好在外面给this指针加上一个const,这样的话const对象可以调用它,非const对象也可以调用它

版本二:不仅支持遍历访问vector里面的内容,还可以去修改vector里面的内容。

begin
        //可读可写
		iterator begin()
		{
			return _start;
		}
		//只读
		const_iterator begin()const
		{
			return _start;
		}
end
        //可读可写
		iterator end()
		{
			return _finish;
		}
		//只读
		const_iterator end()const
		{
			return _finish;
		}
与容量和大小相关的函数
resize

实现resize之前我们首先得熟悉它的使用规则

1.如果n小于当前vector的size,就会将vector的有效数据个数变成n,即更新我们的_start即可。

2.如果n大于vector的size,我们需要在当前vector的后面添加字符val,如果val未给就是当前vector存储数据类型的缺省值(比如int的缺省值就是0,string的缺省值是空字符串),更新_finish的值,直到当前vector有效数据个数等于n为止。

注意:如果n大于vector的话,需要先调用reserve函数增容

        //改变容器中有效数据个数和空间
		void resize(size_t n, T val = T())
		{
			//分情况讨论
			//1.如果n是小于当前容器的size的
			//我们只需要更新一下_finish即可
			if (n < size())
			{
				_finish = _start + n;
			}
			//2.如果n是大于当前容器的size的
			else
			{
				//首先判断一下是否需要增容
				if (n>capacity())
				{
					reserve(n);
				}
				//然后再在我们后面放入val即可
				//缺几个就放几个
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
reserve

同样的实现reserve之前我们也得先熟悉它的使用规则

1.如果n大于当前对象的capacity,则需要将capacity扩大到n

2.如果n小于当前对象的capacity,则什么也不用做。

reserve函数的实现也是比较简单的,首先判断n是否vector的最大容量(若小于当前vector的最大容量则什么都不做),n若大于vector的最大容量我们则需要开辟一块足够的空间,然后将旧空间的数据拷贝过去,再释放旧空间,并将_start等成员变量指向新空间的位置上。

        //只改变空间容量
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				//新开一个足够的空间
				T* temp = new T[n];
				//_start非空的时候才把数据拷过来
				if (_start)
				{
					//把旧空间的数据拷过去
					for (size_t i = 0; i < sz; i++)
					{
						//调用赋值运算符重载
						//实现深拷贝
						temp[i] = _start[i];
					}
					//对于内置类型或者浅拷贝的自定义类型可以使用memcpy去拷贝
					//但是对于需要深拷贝的自定义类型就必须要赋值运算符重载实现深拷贝
					//memcpy(temp, _start, sz*sizeof(T));
					//释放就空间
					delete[]_start;
				}
				//再指向新空间
				_start = temp;
				//注意:_finish这里不能够+size(),因为如果刚开始第一次插入的时候,_finish与_start都是nullptr,
				//但是由于扩容了,_start指向新空间,但是_finish还是nullptr,而size() = _finish-_start的,因此就会出现负数
				//然后一个负数加上一个正数,就会出现非法访问内存
				//因此我们需要再前面先算出size()的值,然后_finish = _start+sz
				_finish = _start + sz;
				_endofstorage = _start + n;

			}
		}

实现reserve函数的时候有两个需要注意的地方:

  • 在进行开辟一块足够的新空间之前,我们需要先提前记录当前容器中有效数据的个数,也就是_finish位置相对于_start位置的距离

​ 由于在我们开辟一块足够的新空间之后,start是指向当前新空间的,而finish却还是指向旧空间的,因此我们如果还是用finish = start+size()的方法去更新finish的指向就会出问题。因为finsih是指向旧空间的,start函数指向新空间我们再调用size函数通过finish - start计算出vector的有效数据个数就会是一个随机值。同时我们更新finish的时候,由于finish = start+size()而当前size函数是一个随机值,那么finsh也会指向一块随机的空间导致出问题。

在这里插入图片描述

  • 拷贝vector旧空间的数据时,不能够使用memcpy函数进行拷贝

​ 大家可能会比较奇怪,为什么不能够使用memcpy函数来拷贝呢?接下来我就来为大家解答这个疑惑

​ 这是因为当vector当中存储的是string的数据时,由于memcpy是内存的二进制格式拷贝,它会将一段内存空间中内容原封不动的拷贝到另外一段内存空间中,这就是一种更深层次的深浅拷贝问题了。

​ 虽然使用memcpy函数reserve出来的新空间与旧空间的内容相同的,但是两块空间里面的string成员变量是指向同一块空间的。由于两块空间里面的string成员变量都是指向同一块空间的,等我们将旧空间的数据都拷贝到新空间之后,释放旧的空间的时候string就会去调用它的析构函数,将旧空间string成员变量指向的空间给释放掉。正是因为这里memecpy函数的拷贝,两块空间里面的string成员变量才会指向同一块空间的,由于旧空间里面的string成员变量所指向的空间已经被释放掉了,那么这个时候新空间里面的string成员变量就会指向一块已经不属于它的空间,如果我们再对成员变量进行操作的话就属于非法访问内存。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

empty

empty函数的作用是判断当前容器是否为空,实现起来比较简单,只需要比较一下_start与_finish是否相等即可

        //判断当前容器是否为空
		bool empty()const
		{
			return _start == _finish;
		}
size

size函数的作用返回vector中有效数据的个数

因为_start是指向vector的起始位置,而_finish是指向最后一个有效数据的下一个位置

因此vector有效数据的个数= _finish - _start

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

capacity函数的作用返回vector容器的容量大小

因为_start是指向vector的起始位置,而_endofstorage是指向最后一个空间的下一个位置

因此vector的容量大小= _endofstorage - _start

        size_t capacity()const
		{
			return _endofstorage - _start;
		}
修改容器内容的相关函数
push_back

push_back函数的作用是在vector的尾部插入一个数据,也就是我们所说的尾插。

首先我们得判断一下是否需要增容,空间不够就先增容,空间足够便直接将数据插入到_finish的位置上即可,因为它是指向最后一个数据的下一个位置的,然后再让我们的_finish++即可

        void push_back(const T& x)
		{
			//判断是否需要增容
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}

			//将数据插入到_finish的位置上
			//再更新_finish
			*_finish = x;
			++_finish;
		}
pop_back

pop_back函数的作用是删除vector尾部的一个数据,也就是我们所说的尾删

我们需要先判断一下当前容器是否为空,容器为空就不能再去删了,如果容器为空还去删就会出问题,容器不为空,只需要让我们的_finish–即可

        void pop_back()
		{
			//容器里面不能为空
			//否则再去删就会出问题
			assert(!empty());
			--_finish;
		}
swap

swap函数的作用是交换两个存储相同数据类型的vector的内容,我们这里实现swap函数可以直接调用库(algorithm)里面的swap模板函数即可,我们如果像调用库里面的swap模板函数的话,需要在swap前面加上"::"(作用域限定符),告诉编译器优先去全局范围找swap函数,如果不加上作用域限定符编译器就会认为你调用的就是你正在实现的swap函数(就近原则)

        // v1.swap(v2)
		void swap(vector<T>& v)
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(_endofstorage,v._endofstorage);
		}
insert

insert函数的作用是在pos位置插入一个值,我们不建议频繁调用它,因为insert是有可能需要挪动数据的(在尾部插入数据的时候不需要挪动数据),挪动数据效率就会降低。

我们首先需要判断一下是否需要增容,空间不够了就需要先增容,但是在增容之前我们需要先算出pos位置相对于_start的距离,增容之后我们再去更新pos的位置。

这也就是为什么我们上一篇文章明明增容之后pos已经指向的旧空间都已经被释放了,但是却还是在新空间里面成功插入了值,因为我们是在insert函数的时候自己在内部做了一些处理,所以增容之后在新空间pos位置上插入了值。

这个时候可能还会有人问,那为什么我们在里面做了处理,但是在外面调用了insert之后,pos就变成了野指针呢?

这是因为我们这里的pos是传值,而不是传引用,因此函数里面pos的改变并不会影响外面的pos。

增容完之后我们还需要将pos位置给空出来,因此我们需要将pos以及pos后面位置的有效数据都向后挪动一个位置,最后再在pos位置上插入x即可。

        void insert(iterator pos, const T& x)
		{
            assert(!empty());
			//判断是否需要增容
			if (_finish == _endofstorage)
			{
				//在增容前先计算出pos位置相对于_start位置的距离
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//增容完成后,再更新一下pos位置
				//这里也就回答了我们昨天,为什么pos增容之后已经是野指针
				//但是却还可以插入值
				//并且内部没有出现迭代器失效问题,但是外部出现了迭代器失效问题
				pos = _start + len;
			}
			//增完容,我们还需要挪动数据
			//把pos位置给空出来
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}
erase

erase函数的作用是删除pos位置的值,然后返回现在pos位置的迭代器。

在删除之前我们首先要判断一下当前容器是否为空,如果为空了再去删除就会出问题。

其实我们这里的删除并不需要我们真的去删除它,而是通过覆达到删除的效果。

所以我们只需要将pos后面的有效数据都向前挪动一个数据,就可以将pos位置的数据覆盖掉,从而达到删除的效果。

最后再返回当前pos位置的迭代器即可。

        iterator erase(iterator pos)
		{
            //容器不能够为空
			iterator end = pos + 1;
			//我们只需要挪动数据
			//覆盖pos位置的值即可
			while (end != _finish)
			{
				*(end - 1) = *end;
				++end;
			}
			--_finish;
			//最后再返回pos位置的迭代器
			return pos;
		}
访问容器的相关函数
operator[]

和string一样,vector也同样支持[]+下标的方式去访问或者修改vector里面的内容,所以我们这里也需要实现两个版本的operator。

版本一:只能通过[]+下标的方式去遍历访问vector里面的内容,不能够修改vector,由于我们这里不会修改成员变量,所以我们最好在外面加上一个const,这样的话非const对象可以调用它,const对象也可以调用它。因此我们需要在外面加两个const,一个const加在返回值的前面,使得我们的返回值不能够被修改,一个给this保证非const对象可以调用它,const对象也可以调用它。

版本二:既能够通过[]+下标的方式去访问vector里面的内容,同时也可以去修改vector里面的内容。

        //可读可写
		T& operator[](size_t pos)
		{
			//pos的位置必须合法
			assert(pos < size());

			return _start[pos];
		}
		//只读
		const T& operator[](size_t pos)const
		{
			//pos的位置必须合法
			assert(pos < size());

			return _start[pos];
		}
vector的实现代码
namespace mlf
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//可读可写
		iterator begin()
		{
			return _start;
		}
		//只读
		const_iterator begin()const
		{
			return _start;
		}

		//可读可写
		iterator end()
		{
			return _finish;
		}
		//只读
		const_iterator end()const
		{
			return _finish;
		}


		//构造函数一
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

		//构造函数二
		vector(size_t  n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			//提前开好空间
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		//构造函数三
		// 类模板的成员函数,还可以再是函数模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//拷贝构造
		vector(const vector& v)
		//v1(v2)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			实现方法一
			使用方法一的时候一定要将指针初始化
			否则reserve的时候会出问题
			//reserve(v.capacity());
			//for (auto e : v)
			//{
			//	push_back(e);
			//}

			//实现方法二
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++)
			{
				//调用赋值运算符重载
				_start[i] = v._start[i];
			}
			//更新指针
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();

		}
		
		//赋值运算符重载
		//v1 = v3
		//实现方法一
		vector<T>& operator=(const vector<T>& v)
		{
			//防止自己给自己赋值
			if (this != &v)
			{
				delete[]_start;
				_start = new T[v.capacity()];
				for (size_t i = 0; i < v.size(); i++)//将容器v当中的数据一个个拷过来
				{
					_start[i] = v._start[i];
				}
				_finish = _start + v.size();
				_endofstorage = _start + v.capacity();
			}
			//支持连续赋值
			return *this;
		}

		赋值运算符重载
		实现方法二
		v1 = v3
		//vector<T>& operator=(vector<T> v)
		//{
		//	swap(v);
		//	return *this;
		//}


		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
			}
			_start = _finish = _endofstorage = nullptr;
		}


		//改变容器中有效数据个数和空间
		void resize(size_t n, T val = T())
		{
			//分情况讨论
			//1.如果n是小于当前容器的size的
			//我们只需要更新一下_finish即可
			if (n < size())
			{
				_finish = _start + n;
			}
			//2.如果n是大于当前容器的size的
			else
			{
				//首先判断一下是否需要增容
				if (n>capacity())
				{
					reserve(n);
				}
				//然后再在我们后面放入val即可
				//缺几个就放几个
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		//只改变空间容量
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				//新开一个足够的空间
				T* temp = new T[n];
				//_start非空的时候才把数据拷过来
				if (_start)
				{
					//把旧空间的数据拷过去
					for (size_t i = 0; i < sz; i++)
					{
						//调用赋值运算符重载
						//实现深拷贝
						temp[i] = _start[i];
					}
					//对于内置类型或者浅拷贝的自定义类型可以使用memcpy去拷贝
					//但是对于需要深拷贝的自定义类型就必须要赋值运算符重载实现深拷贝
					//memcpy(temp, _start, sz*sizeof(T));
					//释放就空间
					delete[]_start;
				}
				//再指向新空间
				_start = temp;
				//注意:_finish这里不能够+size(),因为如果刚开始第一次插入的时候,_finish与_start都是nullptr,
				//但是由于扩容了,_start指向新空间,但是_finish还是nullptr,而size() = _finish-_start的,因此就                   会出现负数
				//然后一个负数加上一个正数,就会出现非法访问内存
				//因此我们需要再前面先算出size()的值,然后_finish = _start+sz
				_finish = _start + sz;
				_endofstorage = _start + n;

			}
		}
		//判断当前容器是否为空
		bool empty()const
		{
			return _start == _finish;
		}

		// v1.swap(v2)
		void swap(vector<T>& v)
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(_endofstorage,v._endofstorage);
		}

		void push_back(const T& x)
		{
			//判断是否需要增容
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}

			//将数据插入到_finish的位置上
			//再更新_finish
			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			//容器里面不能为空
			//否则再去删就会出问题
			assert(!empty());
			--_finish;
		}

		void insert(iterator pos, const T& x)
		{
			//判断是否需要增容
			if (_finish == _endofstorage)
			{
				//在增容前先计算出pos位置相对于_start位置的距离
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//增容完成后,再更新一下pos位置
				//这里也就回答了我们昨天,为什么pos增容之后已经是野指针
				//但是却还可以插入值
				//并且内部没有出现迭代器失效问题,但是外部出现了迭代器失效问题
				pos = _start + len;
			}
			//增完容,我们还需要挪动数据
			//把pos位置给空出来
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}

		iterator erase(iterator pos)
		{
            //容器不能够为空
            assert(!empty());
			iterator end = pos + 1;
			//我们只需要挪动数据
			//覆盖pos位置的值即可
			while (end != _finish)
			{
				*(end - 1) = *end;
				++end;
			}
			--_finish;
			//最后再返回pos位置的迭代器
			return pos;
		}


		//可读可写
		T& operator[](size_t pos)
		{
			//pos的位置必须合法
			assert(pos < size());

			return _start[pos];
		}
		//只读
		const T& operator[](size_t pos)const
		{
			//pos的位置必须合法
			assert(pos < size());

			return _start[pos];
		}

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

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


	private:
		iterator _start;//指向vector中第一个数据的地址
		iterator _finish;//指向最后一个有效数据的后一个位置的地址
		iterator _endofstorage;//指向vector中最后一个空间的下一个位置
	};

以上就是这篇文章的全部内容了,如果觉得文章对你有帮助的话可以三连一波支持一下作者,同时欢迎各位大佬指正文章的不足。

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值