C++入门之vector的模拟实现

Posted cls-evd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门之vector的模拟实现相关的知识,希望对你有一定的参考价值。

目录

一、vector的大致框架

二、vector的模拟实现 

构造函数

默认构造函数

使用迭代器初始化的构造函数

为什么这里的迭代器区间取名叫InputIterator呢?

拷贝构造函数

传统写法

现代写法

operator=的实现

现代写法

size()的实现

capacity()的实现

operator [ ] 的实现

push_back()的实现

方法一:提前将size算出来。再去更新_start

方法二:提前将_finish算出来,注意_finish应该是tmp+size();

关于tmp用不用手动delete的问题

迭代器的实现

reserve的实现

resize的实现

const引用延长生命周期 

resize的实现

pop_back的实现

insert的实现

迭代器失效 

erase的实现

迭代器失效 

memcpy的问题

三、完整代码


一、vector的大致框架

vector源代码的大致框架,vector本质就是一个模板类

vector的成员变量不再是我们熟悉的size,capacity,而是变成了功能一致的三个指针

大体框架

namespace pxl

	template<class T>
	class vector
	
	public:
        typedef T* iterator;
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	;

二、vector的模拟实现 

构造函数

默认构造函数

		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		

使用迭代器初始化的构造函数

(一个类模板的成员函数,又可以是一个函数模板)

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

如果不用这个函数模板,意思就是只能使用vector的迭代器进行初始化,而不能使用其他类型的迭代器进行初始化,所以要用到一个模板,可以传任意类型的迭代器。

为什么这里的迭代器区间取名叫InputIterator呢?

函数模板的模板参数要传迭代器区间时,是存在命名规范的。

迭代器是用来访问容器的,产生这么多不同类型的迭代器在于容器有不同类型的结构。

 这些迭代器从下到上属于下面包含上面的关系

如果函数参数是随机迭代器,那么实参就只能传随机迭代器。

如果函数参数是单向迭代器,那么实参就可以传单向迭代器,双向迭代器,随机迭代器。

如果函数参数是只写迭代器,那么实参就可以传只写迭代器,只读迭代器,单向迭代器,双向迭代器,随机迭代器。

eg:以sort为例,它是只能传随机迭代器的

#include<iostream>
#include<vector>
#include<list>
#include<algorithm>
using namespace std;

int main()

	pxl::test2();
	vector<int> v;
	v.push_back(5);
	v.push_back(4);
	v.push_back(3);
	v.push_back(2);
	v.push_back(1);
	sort(v.begin(), v.end());

	list<int> lt;
	lt.push_back(5);
	lt.push_back(4);
	lt.push_back(3);
	lt.push_back(2);
	lt.push_back(1);

	sort(lt.begin(), lt.end());

对于上面这段代码,语法上是没有任何问题的,因为迭代器是一个模板,可以传任何类型,但是sort的底层是快排,快排需要三数取中需要用到 - 操作 但是list的迭代器不满足-操作,所以最终要报错。因为vector底层是一个连续的数组,而list底层是一个链表,所以vector支持减操作,list不支持减操作。

拷贝构造函数

传统写法

以v2(v1)为例,开一块和v1一样大的空间,再把v1的数据拷贝到这块空间上去

		vector(const vector<T>& v)
		
			_start = new T[v.capacity()];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();

			memcpy(_start, v._start, v.size() * sizeof(T));
		

现代写法

借助迭代器初始化的构造函数,用v1创建一个临时变量tmp,再将v2与tmp交换,tmp出了作用域调用析构函数销毁

		void swap(vector<T>& v)
		
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		


		//现代写法
		vector(const vector<T>& v)
		
			vector<T> tmp(v.begin(), v.end());
			/*swap(_start, tmp._start);
			swap(_finish, tmp._finish);
			swap(_endofstorage, tmp._endofstorage);*/

			swap(tmp);
		

operator=的实现

现代写法

以v3=v1为例,利用传值传参拷贝构造v1,此刻的v就是v1,然后将v3与v1交换,v是临时对象出了作用域就会调用析构函数,析构掉v1

		void swap(vector<T>& v)
		
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
				
        vector<T>& operator=(const vector<T> v)  
		
			/*swap(_start, v._start);
			swap(_finish, v._finish);
			swap(_endofstorage, v._endofstorage);*/

			swap(v);
			return *this;
		

size()的实现

观察上图,易得:

 size_t size() const 

	return _finsih - _start;

capacity()的实现

观察上图,易得:

size_t capacity() const 

	return _endofstorage - _start;

operator [ ] 的实现

		T& operator[](size_t i)
		
			assert(i < size());
			return _start[i]; //指针充当数组
		

T代表模板,_start的类型是T*,所以返回值是T&  

const版本只能读不能写,同时适合const对象传参

		const T& operator[](size_t i) const
		
			assert(i < size());
			return _start[i]; //指针充当数组
		

push_back()的实现

  • 首先判断容量是否足够,如果容量不够就进行增容。
  • 如果原来的数组为空,直接在_finish位置插入即可,如果原来的数组不为空并且容量不够了就将旧数据拷贝到新的空间,然后在_finish位置插入.
		void push_back(const T&x)
		
			if (_finish == _endofstorage)
			
				size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[Newcapacity];
				if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
				
					memcpy(tmp, _start, sizeof(T) * size());
                    delete[] _start;
				
				_start = tmp;
			

			*_finish = x;
			++_finish;
		

思路看似没有问题,但是调试过后发现代码崩溃了,原因就是_finish与_endofstorage是空指针 

测试样例:

默认构造

所以我们就需要在最开始的时候通过tmp算出来_finish与_endofstorage、但我们加上以后还会有问题

原因就是我们实现的size是_finish - _start;,两个一抵消,所以_finish永远是0(一个空指针).

解决方法:

方法一:提前将size算出来。再去更新_start

		void push_back(const T&x)
		
			if (_finish == _endofstorage)
			
				size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				size_t sz = size(); //提前算出size(),避免出错
				T* tmp = new T[Newcapacity];
				if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
				
					memcpy(tmp, _start, sizeof(T) * size());
                    delete[] _start;
				
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + Newcapacity;
			

			*_finish = x;
			++_finish;
		

方法二:提前将_finish算出来,注意_finish应该是tmp+size();

		void push_back(const T&x)
		
			if (_finish == _endofstorage)
			
				size_t Newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[Newcapacity];
				if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
				
					memcpy(tmp, _start, sizeof(T) * size());
                    delete[] _start;
				
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + Newcapacity;
			

			*_finish = x;
			++_finish;
		

更改后,监视的过程 

关于tmp用不用手动delete的问题

tmp是不需要我们手动在push_back内部进行delete的。如果tmp是内置类型,tmp出了作用域后不作任何处理,但是我们已经将tmp赋值给了_start,我们又实现了vector的析构函数,析构函数里又会释放这个_start,所以等到vector对象的生命周期结束的时候,vector就会自动释放_start,从而释放了tmp,因为它俩指向的都是同一块空间。如果tmp是自定义类型,tmp除了作用域会自动调用它的析构函数,进行释放。所以是不需要我们在push_back内部手动释放的。

迭代器的实现

对于vector来说就是原生指针

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

reserve的实现

如果输入的容量大于capacity就进行扩容

		void reserve(size_t n)
		
			if (n>capacity())
			
				size_t sz = size(); //提前算出size(),避免出错
				T* tmp = new T[n];
				if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
				
					memcpy(tmp, _start, sizeof(T) * size());
                    delete[] _start;
				
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			
		

实现完reserve后push_bcak就可以进行复用

		void push_back(const T&x)
		
			if (_finish == _endofstorage)
			
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			

			*_finish = x;
			++_finish;
		

resize的实现

这的缺省值不能给0,因为T可能是多种类型,如果是int可以给0,但是其他类型如string等就不能给0了,这里我们给一个T() ,如果是int就是0,int*就是空指针,如果是string就是string的匿名对象,调用string的默认构造函数。 匿名对象的生命周期只在当前这一行但是用const引用修饰以后就会延长它的生命周期 。模板出现以后可以认为内置类型也有默认构造函数

const引用延长生命周期 

关于const引用可以延长生命周期可以通过以下例子进行验证

class A

public:
	A()
	

	~A()
	
		cout << "~A()";
	
;

void _test()

	A(); //过了这一行就会调用析构函数
	const A& x = A(); //出了这个函数作用域才回去调用析构函数

 

resize的实现

  • 1.如果n小于当前的size,则数据个数缩小到n
  • 2.如果n大于于当前的size,则说明空间不够需要增容,并且将size扩大到n
		void resize(size_t n, const T& val = T())
		
			if (n < size())
			
				_finish = _start + n;
			
			else 
			
				if (n > capacity())
				
					reserve(n);
				
				while (_finish != _start + n)
				
					*_finish = val;
					++_finish;
				

			
		

pop_back的实现

只要--_finish即可,但是也不能一直减_finish,当_finsih==_start的时候已经说明没数据了。

		void pop_back()
		
			assert(_finsih > _start);
			--_finish;
		

insert的实现

在指定pos位置进行插入

        void insert(iterator pos, const T& x)
		
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _endofstorage)
			
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			

			iterator end = _finish - 1;
			while (end >= pos)
			
				*(end + 1) = *end;
				end--;
			
			*pos = x;
			++_finish;

		

eg1:经过这次验证也确实莫得问题 

eg2:但是当我们多插入了一个数后就会发现出错了

迭代器失效 

本质就是迭代器失效

原因就是eg1中只有3个数据,这时候的总空间是4(我们的代码默认第一次开4个空间),所以用我们当前的逻辑不会出错,但是eg2中有4个数据,在插入的时候就需要进行扩容,而扩容会新开一段空间, 这时候_start和_finish都改变了指向。

这两段空间pos是可以访问到的,当把一块内存释放了以后是将它还给系统了(将它的使用权还给系统了),此时这个pos还指向一块被释放的空间,在迭代器的角度,这个迭代器就失效了,然后在解引用pos就是野指针的访问。

解决方法法:

对这个pos进行更新,提前算好pos与_start的位置,在扩容的时候更新pos

			if (_finish == _endofstorage)
			
				//扩容会导致pos的失效,扩容前需要更新一下pos
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			

但是此时的问题还没有完全解决,insert内部的pos的失效我们已经解决,但是我们调用insert时传的实参it(为了区分我们用it代替pos)也存在迭代器失效的问题

如果我们还想要再去使用这个it,it就失效了。

这里的原因就是因为insert的实现是传值返回,形参的改变不会影响实参。如果insert中发生了扩容,那么会导致it指向空间被释放,it本质就是一个野指针,这种问题,我们也叫做迭代器失效.

解决方法:

用一个返回值接收新的位置的pos

		iterator insert(iterator pos, const T& x)
		
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _endofstorage)
			
				//扩容会导致pos的失效,扩容前需要更新一下pos
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			

			iterator end = _finish - 1;
			while (end >= pos)
			
				*(end + 1) = *end;
				end--;
			
			*pos = x;
			++_finish;

			return pos;
		

通过接收返回值解决it失效的问题 

注意这里不能传引用解决,因为会导致很多的问题

如果传引用,这种插入是不可行的,因为beign()返回的是临时对象,临时对象具有常性。那么传const引用呢?这也是不行的,因为insert内部的pos在扩容的时候是需要改变的,所以不能传引用。

erase的实现

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

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

测试 

迭代器失效 

同样erase也会存在迭代器失效的问题 。

现在我们实现要求删除v中所有的偶数

1.0 v中数据是1 2 3 4 崩溃

最后一个数是偶数,会导致erase以后,it意义改变,再++一下,导致it和end结束判断而错过。

那能否改为小于解决这个问题呢?

while(it < v.end())

    //...

答案也是不行的因为这不符合迭代器的使用规则,不对任何容器的迭代器都通用。 

2.0 v中数据是1 2 3 4 5正常

  

3.0 v中数据是1 2 4 5会没删完

 

erase(it)以后,it指向位置的意义就已经改变了。直接++it可能会导致一些意料之外的结果。如果是连续的偶数,会导致后一个偶数没有判断,没有被删掉。再其次,erase删除有些vector版本的实现,不排除他会缩容,如果是这样erase(it) 以后it也可能是野指针,跟insert类似。ps:SGI和PJ版本的vector都没有这样做

解决方法:

导致上述三种问题,本质都是erase(it)以后it的意义改变了,再去++it是不对的。其实就是it失效了

 对erase增加一个返回值,返回删除后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;
		

这部分判断改为这样,就可以解决这个问题

PS:写成这样同样是无法解决问题的

总结:vector的迭代器失效主要发生在insert和erase,只要使用迭代器访问访问的容器,都可能存在迭代器失效,string失效的场景与vector完全一样,但是string很少失效,因为它的插入与删除主要使用的是下标插入的重载函数,很少用迭代器进行插入与删除。

memcpy的问题

对于这段代码是没有问题的

但是如果多插入一个,程序就崩溃了。

目前版本的push_back拷贝用的是memcpy 

这是因为memcpy是一个浅拷贝,对于内置类型没问题,但对于自定义类型的string就会发生同一块空间被析构函数释放两次的问题。

对于内置类型,tmp出了作用域不做任何处理;对于自定义类型,tmp出了作用域会调用它的析构函数。delete[ ] _start析构了一次自定义类型,tmp出了作用域也要析构一次自定义类型,所以就造成了一次空间被释放了两次。所以最终程序崩溃。

解决方法:

不用memcpy浅拷贝,而是进行深拷贝

		void reserve(size_t n)
		
			if (n>capacity())
			
				size_t sz = size(); //提前算出size(),避免出错
				T* tmp = new T[n];
				if (_start) //等于空不需要拷贝,将旧数据拷贝到新空间
				
					for (size_t i = 0; i < sz; i++)
					
						//T是int,一个一个拷贝没问题
						//T是自定义类型	,一个一个拷贝调用的是T的深拷贝赋值=
						tmp[i] = _start[i];
					
					delete[] _start;
				
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			
		
		void push_back(const T&x)
		
			if (_finish == _endofstorage)
			
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			

			*_finish = x;
			++_finish;
		

三、完整代码

namespace pxl

	template<class T>
	class vector
	
		
	public:

		typedef T* iterator;
		typedef const T* const_iterator;


		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		

		//传统写法的拷贝构造
	/*	vector(const vector<T>& v)
		
			_start = new T[v.capacity()];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
			memcpy(_start, v._start, v.size() * sizeof(T));
			//这里的memcpy和reserve一样
		*/

		//迭代器区间构造
		//一个类模板的成员函数,又可以是一个函数模板
		template <class InputIterator>  //为什么叫inputiterator,迭代器区间的引擎规范
		//函数模板的模板参数要传送迭代器区间,存在命名规范
		//input_ 只写迭代器    output_ iterator只读迭代器 forward_ 单向迭代器(单链表)   bidirectional_双向迭代器
		//randomaccess_ 随机迭代器

		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		
			while (first != last)
			
				push_back(*first);
				++first;
			
		

		void swap(vector<T>& v)
		
			std::swap(_start, v._start);
			std::swap(_finish, v._start);
			std::swap(_endofstorage, v._endofstorage);
		

		现代写法的拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		
			vector<T> tmp(v.begin(), v.end());
			
			//this->swap(tmp);
			swap(tmp);

		

		//opreator
		vector<T>& operator = (vector<T> v)
		
			swap(v);
			return *this;
		


		~vector()
		
			if (_start)
			
				delete[] _start;
			    _start = _finish = _endofstorage = nullptr;
			
		

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

		const_iterator begin() const
		
			return _start;
		
		const_iterator end() const
		
			return _finish;
		


		const T& operator[](size_t i) const
		
			assert(i < size());
				return _start[i];
		

		T& operator[](size_t i)
		
			assert(i < size());
				return _start[i];
		

		size_t size() const
		
			return _finish - _start;
		

		size_t capacity() const
		
			return _endofstorage - _start;
		

		void push_back(const T& x)
		
			if (_finish == _endofstorage)
			
			/*	size_t newCapacity =( capacity() == 0 ? 4 : capacity() * 2);
				size_t sz = size();
				T* tmp = new T[newCapacity];

				if (_start)
				
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + newCapacity;*/

				reserve(capacity() == 0 ? 4 : capacity() * 2);
			
			*_finish = x;
			++_finish;
		

		void reserve(size_t n)
		
			if (n > capacity())
			
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				
					//使memcpy对自定义类型又浅拷贝的问题
					//memcpy(tmp, _start, sizeof(T) * size());

					for (size_t i = 0; i < sz; i++)
					
						//T是int,一个一个拷贝没问题
						//T是自定义类型	,一个一个拷贝调用的是T的深拷贝赋值=
						tmp[i] = _start[i];
					
					delete[] _start;
				
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			
		

		//const引用,延长匿名对象的生命周期,匿名对象传缺省值,T这个类型生成的匿名对象,
		//模板出现以后可以认为内置类型也有构造函数
		void resize(size_t n, const T& val = T())
		
			if (n < size())
			
				_finish = _start + n;
			
			else
			
				if (n > capacity())
				
					reserve(n);
				

				while (_finish != _start + n)
				
					*_finish = val;
					++_finish;
				
			

		

		void pop_back()
		
			assert(_finish > _start);
			--_finish;
		

		//vector的迭代器失效主要发生在insert和erase
		//只要使用迭代器访问访问的容器,都可能迭代器失效
		//string失效的场景与vector完全一样,但是string很少失效,因为它的插入与删除使用下标
		iterator insert(iterator pos, const T& x)
		
			assert(pos >= _start);
			assert(pos <= _finish); //=是为了尾插
			//满了就扩容
			if (_finish == _endofstorage)
			
				//扩容会导致pos失效,扩容需要更新一下pos
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity * 2);
				pos = _start + len;
			

			iterator end = _finish - 1;
			while (end >= pos)
			
				*(end + 1) = *(end);
				--end;
			
			*pos = x;
			++_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;
		


	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;


	;


	void test()
	
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		vector<int> v2(v);

		for (size_t i = 0; i < v.size(); i++)
		
			cout << v[i];
		
		cout << endl;
		for (auto e : v)
		
			cout << e;
		
	

c++——STL容器之vector的使用和模拟实现

目录

1.vector的概述

2.vector常用接口

2.1 构造函数

2.2 迭代器的使用:

2.3 修改的接口

push_back

pop_back

insert

erase

 find

reverse

 2.4 关于容量接口

resize

reverse 

2.5 数据的访问

3 vector的迭代器

迭代器失效的问题

4. vector的模拟实现

4.1reserve接口的实现

 4.2 resize

4.3 insert

 4.4 push_back

4.5 erase

 4.6 pop_back

4.7 构造函数


1.vector的概述

vector本质是数据结构中的线性表,它是动态空间的数组,如果vector中的空间满了以后,它会先开辟一块更大的新空间,然后将旧空间的数据拷贝给新空间,再把旧空间的给释放掉。vector开辟的新空间的容量一般是旧空间的容量的两倍或者1.5倍,不同的编译器增容方式可能是不同的。例如再g++中vector是以两倍增长的,vs中vector是以1.5倍增长的。

vector可以储存任意类型的数据,包括自定义类型,当定义vector的时候,需要指明vector存储的数据是什么类型的,定义格式:

 vector<类型> 名字;

	std::vector<int> v1;//v1是存储int类型的数据
	std::vector<string> v2;//v2存储的是string类型的数据
	std::vector<char> v3;//v3是存储char类型的数据

2.vector常用接口

vector的接口有很多,在这里我就不一 一讲解,我只讲一些我们经常会用到的接口,如果遇到哪个接口不懂的话,自己可以去查一下文档:vector - C++ Reference

2.1 构造函数

vector无参的构造函数
vector size_type n, const value_type& val = value_type()
构造并初始n个val
vector (const vector& x);
拷贝构造
vector (InputIterator fifirst, InputIterator last);
利用迭代器进行初始化构造
	std::vector<int> v1;//无参的构造函数
	std::vector<int> v2(10, 2);//构造并初始化10个2
	std::vector<int> v3(v2.begin(), v2.end());//利用v2的迭代器初始化v3
	vector<int> v4(v2);//拷贝构造

2.2 迭代器的使用:

  正向迭代器:begin+end  返回的的类型是iterator/const_iterator

  反向迭代器:rbegin+rend 返回的类型是reverse_iterator/const_reverse_iterator

 利用迭代器遍历vector v1,v1中包含元素有 1 ,2 ,3,4,5

void test(vector<int> v)
{
	vector<int>::iterator it = v.begin();
	cout << "正向迭代器遍历:";
	while (it != v.end())//正向迭代器遍历
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;

	vector<int>::reverse_iterator rt = v.rbegin();
	cout << "反向迭代器遍历:";
	while (rt != v.rend())//反向迭代器遍历
	{
		cout << *rt << ' ';
		rt++;
	}	
	cout << endl;
}

输出:

正向迭代器遍历:1 2 3 4 5

反向迭代器遍历:5 4 3 2 1

2.3 修改的接口

                  push_back        在末尾插入数据
                  pop_back        删除末尾的数据
                   insert          在position之前插入val
                  erase        删除position位置的数据
                  find          查找的某个数据的位置
               operator[ ]            像数组一样去访问vector
              swap          与另一个vector进行交换
            reverse        将vector中的数据进行逆置

push_back

void push_back (const value_type& val);

push_back接口的参数只有一个,它可以在vector的末尾插上一个数据。

pop_back

void pop_back();

pop_back接口没有参数,它直接删除vector末尾的一个数据。

insert

insert有3种传参的方式。

方式一:

 iterator insert (iterator position, const value_type& val);

在position之前插入一个val。position是迭代器类型

方式二

void insert (iterator position, size_type n, const value_type& val);

在position之前插入n个val。positon是迭代器类型

方式三

template <class InputIterator>

void insert (iterator position, InputIterator first, InputIterator last);

position 和first,last都是迭代器类型。

将一个vector的[first,last)区间的数据插入到另一个相同类型的vector的position位置之前。

 pisition不会随着数据的移动而移动。

erase

erase有两种传参的方式:

iterator erase (iterator position);

方式一:删除position位置的数据、

iterator erase (iterator first, iterator last);

方式二:删除迭代器区间 [ first , lasr ) 中的元素。

 find

template <class InputIterator, class T>

InputIterator find (InputIterator first, InputIterator last, const T& val);

在[ first , last )区间查找val,找到了就返回相应的迭代器,找不到就返回last。

find不是vector中的中的接口,是算法中的函数,只要有迭代器都可以通过find进行查找

reverse

template <class BidirectionalIterator>

void reverse (BidirectionalIterator first, BidirectionalIterator last);

传vector的迭代区间,将vector的迭代区间中的元素进行逆置,例如:将一个vector中1 2 3 4 5 进行逆置就成 5 4 3 2 1

	std::vector<int> v1;//无参的构造函数
    v1.push_back(1);//尾插
	v1.push_back(2);//尾插
	v1.push_back(3);//尾插
	v1.push_back(4);//尾插
	v1.push_back(5);//尾插

	//查找3的位置,并将返回的迭代器存储在pos
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	v1.insert(pos, 30);//在pos位置之前插入30
	reverse(v1.begin(), v1.end());//逆置vector
	//查找3的位置,并将返回的迭代器存储在it
	vector<int>::iterator it = find(v1.begin(), v1.end(), 4);
	v1.erase(it);//删除it位置的数据

        

 2.4 关于容量接口

             size                       获取数据个数
         capacity                获取容量的大小
          empty             判断vector是否为空
          reserve            改变vector的capacity
           resize

            改变vector的size

resize


void resize (size_type n, value_type val = value_type());

使size到n个,如果不足本身的size不足n,则用val补齐vector到n个数据。如果本身的size大于n,则不需要进行任何操作。

reverse 

void reserve (size_type n);

使vector有n个容量空间,如果原本的capacity大于n,则不会缩容。

	std::vector<int> v;
	cout << v.capacity() << endl;//输出0
	cout << v.size() << endl;//输出0
	v.reserve(10);//预留10个capacity的空间大小
	cout << v.capacity() << endl;//输出10
	cout << v.size() << endl;//输出0
	v.resize(20,10);//预留20个数据
	cout << v.capacity() << endl;// 输出20
	cout << v.size() << endl;//输出20

2.5 数据的访问

operator[ ]可以像数组下标去访问vector中的元素
front返回vector中的第一个数据
back返沪vector中最后一个数据

3 vector的迭代器

vector是一个连续的空间,它的迭代器就是原生指针,因为vector中的原生指针可以满足迭代器 operator*,operator[ ],operator++,operator--,operator+=,operator-=的操作,所以vector的迭代器就只需要给原生指针起个别名就够了。如下:

  template<class T>
    class vector
    {
    public:
        typedef T* iterator; //vector的迭代器就是原生指针
        typedef const T* const_iterator;

      ...

}

迭代器失效的问题

在我们用vector中,经常会碰到迭代器失效的问题,那么什么是迭代器失效呢?

我们先来看一段代码,如下:

void test2()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(10);
	v.push_back(4);
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	v.insert(pos,10);
	v.insert(pos, 20);
}

这段代码在vs运行下会奔溃掉,为什么呢?

 

 当我们删除掉2后,pos指向的位置的值就变为10,那么我们pos的意义也就改变了,此时的pos就迭代器就已经失效了,当我们再去使用它的时候,vs编译器就检查出来。

那么我再来看一下下面这个场景:

上面pos已经是野指针,因为旧空间被释放掉所以不知道它指向的是什么值。所以该指针旧已经失效了。

所以迭代器失效有两种方式:

一种是使用insert,erase使迭代器指向的数值发生改变,此时的迭代器就失去意义。这种有

些编译器会检查例如vs,有些编译器不会检查,例如g++

另一种是插入数据的时候,容量满的时候会开辟新空间,旧空间被释放,导致pos指向的数据

不知道是什么,此时的pos成了野指针,这个任何编译器都会报错。

4. vector的模拟实现

namespace sjp
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator; //vector的迭代器是可以修改的
		typedef const T* const_iterator;

        iterator begin()//返回vector迭代区间的头
		{
			return _first;
		}
		iterator end()//返回vector的迭代区间的尾
		{
			return _end;
		}

		const_iterator begin()const
		{
			return _first;
		}

		const_iterator end()const
		{
			return _end;
		}

		size_t size()const//返回vector的数据个数
		{
			return _end - _first;
		}

		size_t capacity()const//返回vector的容量
		{
			return _end_of_storage - _first;
		}

        bool empty()const//判断vector是否为空
		{
			return _first == _end;
		}

		const T& operator[](size_t i)const
		{
			return *(_first + i);
		}

		T& operator[](size_t i)
		{
			return *(_first + i);
		}

		void clear()//清空vector
		{
			_end = _first;
		}


		const T& back()const//返回vector最后一个元素
		{
			return *(back - 1);
		}


		const T& front()const//返回vector第一个元素
		{
			return *(front);
		}


	private:
		iterator _first;//表示目前使用空间的头
		iterator _end;//表示目前使用空间的尾
		iterator _end_of_storage;//表示目前可用空间的尾
	};

在上面接口中,我们发现

1.有些接口在后面需要加上const,例如 empty ,size, capacity这些接口,因为调用这些接口的时候不会修改它的成员变量,所以我们加上const可以防止它的成员变量被修改,并且可以让const对象和非const对象都可以调用这些接口(注意非const对象是可以调用const的接口,但const对象不能调用非const的接口

2.有些接口需要实现const和非const的版本,例如begin ,end,operator[]这些接口,

非const对象调用非const的接口,返回的对象是可读可写的,const对象调用的是const的接口

返回的对象是只能读。例如,const对象返回的也是const的迭代器,就是不能通过迭代器去修改它指向的值。

4.1reserve接口的实现

     void reserve(size_t n)
		{
			size_t sz = size();//先保存原本的size
			if (capacity() < n)
			{
				T* tmp = new T[n];//重新开一块空间
				if (_first)//如果_first不为空指针
				{
					for (int i = 0; i < size(); i++)//将原空间的值拷贝给新空间
					{
						tmp[i] = _first[i];
					}
					delete[] _first;//释放旧空间
				}
				//让迭代器指向新空间
				_first = tmp;
				_end = _first + sz;
				_end_of_storage = _first + n;
			}
		}

这里有一个问题,上面的原空间的值拷贝给新空间中的:

                    for (int i = 0; i < size(); i++)
                    {
                        tmp[i] = _first[i];
                    }

可不可用memcpy(tmp, _first, sz*sizeof(T));来替代

memcpy进行的浅拷贝,当vector存储的像string开辟动态空间的之类就会出错

 

 也就是说memcpy只能拷贝string中的指针,没有将拷贝string的存储字符串的空间,当旧空间的中的存储的指针被释放掉后,同时指针指向的子符串也会被释放掉。

那这段代码为什么可以呢?

   for (int i = 0; i < size(); i++)
   {
              tmp[i] = _first[i];                
   }

 4.2 resize

		void resize(size_t n,T val=T())
		{
			if (n > size())
			{
				if (n > capacity())//如果预留数据个数大于容量则需要增容
				{
					reserve(n);
				}
				size_t sz = n - size();//将数据补齐到n个
				for (int i = 0; i < sz; i++)
				{
					push_back(val);
				}
			}
		}

 如果n大于size的时候就插入val,再进行判断,如果数据大于capacity的时候就需要进行增容,最后将val值补齐使vector的数据个数为n个。

4.3 insert

 		void insert(iterator pos,const T& x)
		{
			assert(pos >= _first && pos <= _end);
			if (_end == _end_of_storage)//判断容量是否满了
			{
				size_t lenth = pos - _first;//如果换新空间,需要保留pos在vector的位置
				int newcapacity = capacity() == 0 ? 5 : capacity() * 2;
				reserve(newcapacity);//预留新空间
				pos =_first+lenth;//跟新pos
			}
			//将x插入到pos位置的前面
			iterator cur = end();
			while (cur != pos)
			{
				*(cur) = *(cur-1);
				cur--;
			}
			*pos = x;	
			_end += 1;//数据个数再加一
		}

 

上面提到了insert迭代器会失效的问题,那么我们可不可以对insert适当修改一下使我们使用insert

不会发生迭代器失效的问题?

只需要在上面的代码的基础,在pos迭代器加上引用&,然后插入数据后,pos加1,这样插入数据后,pos就可以一直指向原本那个数据。

 4.4 push_back

		void push_back(const T& x)//尾插,直接在最后一个位置插入数据即可
		{
			insert(_end, x);
			//if (_end == _end_of_storage)
			//{
			//	int newcapacity = capacity() == 0 ? 5 : capacity() * 2;
			//	reserve(newcapacity);
			//}
			//*_end = x;
			//++_end;
		}

4.5 erase

		iterator erase(iterator pos)
		{
			assert(pos < _end);	
			iterator cur = pos;//将pos位置后的所有数据向前挪一步
			while (cur != _end - 1)
			{
				*cur = *(cur + 1);	
				cur++;
			}
			_end--;//数据个数再减一
			return pos;
		}

 4.6 pop_back

		void pop_back()
		{
			--_end;
		}

4.7 构造函数

		vector(size_t n, T val)//构造n个val值的vector
		{
			reserve(n);//先预留n个数据的空间
			for (int i = 0; i < n; i++)//再将val存满这块空间
			{
				push_back(val);
			}
		}
      
            
         //_first,_end,_end_of_strage赋值为空指针是因为
         //capacity是通过_end_of_storage和_first进行计算的
		vector()//无参的拷贝构造
		 :_first(nullptr),_end(nullptr),_end_of_storage(nullptr)
		{

		}

        vector(iterator first, iterator end)//利用迭代器区间构造vector
			:_first(nullptr), _end(nullptr), _end_of_storage(nullptr)
		{
			while (first != end)
			{
				push_back(*first);
				first++;
			}
		}

        //v2(v1);
		vector(const vector<T>& v)//拷贝构造函数
			:_first(nullptr),_end(nullptr),_end_of_storage(nullptr)
		{
			reserve(v.capacity());//预留v1相同的空间
			for ( auto e: v)//然后将v1中的数据一个一个的推进v2
			{
				push_back(e);
			}
		}

        //v2=v1
		vector<T>& operator=(const vector<T>& v)//赋值构造函数
		{
			vector<T> tmp(v);
			swap(_first, tmp._first);//不能用用=赋值进行赋值,tmp出函数作业域或被析构掉。
			swap(_end, tmp._end);
			swap(_end_of_storage, tmp._end_of_storage);
			return *this;
		}

v1=v2,将v1赋值给v2,需要将v1的空间大小和值跟v2是一模一样的,在赋值过程中,先拷贝构造一个跟v1一模一样的tmp,然后将tmp和v2中_first,_end, _end_of_storage交换过去,出函数作用域的时候,tmp会被析构函数给释放掉。此时v1的空间大小和值跟v2是一模一样的。

总结:在上面中的push_back,pop_back,insert,erase,clear等接口中在后面不需要加上const,因为const对象本身不能被修改的,const的vector中的数据个数和数据都不能被改变,所以说const对象不能调用这些接口。

好了,今天的知识就分享到这里,喜欢的小伙伴们可以给博主点个赞 ,或者点个关注,大家一起努力学习,共同进步。

以上是关于C++入门之vector的模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

C++初阶vector(中)

C++初阶vector(中)

C++从入门到入土第十三篇:vector的模拟实现

C++入门 vector的使用 + 进阶模拟实现

C++从青铜到王者第十篇:STL之vector类的模拟实现

c++——STL容器之vector的使用和模拟实现