C++:vector底层剖析


前言

在上篇文章中,我们介绍了vector的使用,在本篇文章中我们将会详细介绍一下vector的底层逻辑,我们会对vector有更深层次的理解

成员变量

在vector中,我们可以是任意类型,所以我们要采用灵活的方式。在C语言中,我们采用typedef的方式改变存储类型。在C++中进入了模版的概念,我们采用模版来实现。

在C语言板块,我们采用三个变量记录vector的变化,分别是int size,int capacity,int *a;
我们如果去查看vector的底层逻辑,我们就会发现,vector底层采用三个指针记录数据以及空间的变化,指针用迭代器的方式实现

	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T*  const_iterator;
	private:
	        //内置成员变量声明给缺省值
			iterator _start=nullptr;
		    iterator _finish=nullptr;
			iterator _endofstorage=nullptr;
	};

我们来简单介绍一下这几个指针:
_start是vector中元素初始位置
_finish是vector中元素的结束位置
_endofstorage是vector中的容量大小

在这里插入图片描述
侯捷老师《STL原码剖析》这本书画图的,书上的展示图如下:
在这里插入图片描述

成员函数

vector ()

我们在三个成员定义时已经进行了初始化操作

size_t size()

这个函数是用来计算vector中数据的大小

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

size_t capacity()

这个函数是用来计算vector中容量的大小

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

iterator begin()和const_iterator begin()const

这个函数用来记录第一个元素的位置

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

iterator end()和const_iterator end()const

这个函数用来记录最后一个元素的下一个位置

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

~vector()

这个函数用来对空间进行清理

		~vector()
		{
			if (_start)//判断是否有资源需要清理
			{
				delete[]_start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

void push_back(const&T val)

这个函数是用来在尾部插入数据

void  push_back(const T& val)
{
    //首先判断是否有空间,进行插入数据
	if (_finish == _endofstorage)
	{
	     //空间不足,需要开空间,提前计算好需要多大的空间
		size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
		//我们一般采用reserve函数进行开空间
		//因为这个函数是专门用来进行对空间的开辟的
		reserve(newcapacity);
	}
	*_finish = val;
	_finish++;
}

vector(const vector& v)

拷贝构造

		vector<T>(const vector<T>& v)
		{
			//开空间+尾插
			reserve(v.capacity());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

vector& operator=(vector v)

赋值功能
我们可以开辟一块空间,在一个个把值拷贝过去,再释放不用的空间,下面我们采取一种灵活的方式

我们已tmp[i]=_start[i]为例
vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

vector v这个地方不能加引用!!!
我们来看一下具体的实现
🌟 在调用之前,会发生拷贝,拷贝一份相同的内容
在这里插入图片描述

🌟 把v和tmp进行交换,指针指向发生变化
在这里插入图片描述

🌟 v是局部变量,出了作用域进行销毁,完成深拷贝工作
我们不开空间而是直接构造

void swap(vector &v)

交换两个vector

		void swap(vector<T> &v)
		{
		//仅仅通过交换两个指针的位置就可以解决 
		//必须指定是库里的,要不然就会发生死递归,最终栈溢出
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

void reserve(size_t n)

这个函数是用来对空间进行开辟的,开n个大小的空间

void reserve(size_t n)
{
	//判断是否需要开空间
	if (n > capacity())
	{
		//开空间
		T* tmp = new T[n];
		if (_start)
		{
			//拷贝之前的数据到新空间
			memcpy(tmp, _start, sizeof(T) * n);
			//释放旧空间
			delete[]_start;
		}
		//更新指针

		_start = tmp;
		_finish = _start +size();
		_endofstorage = _start + capacity();
	}
}

我们来测试一下下面这段代码


	void test1()
	{
		vector<int>v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (auto& e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

我们会发现,直接报错了,经过调试我们发现了错误所在
在这里插入图片描述
我们来画图看一看
在这里插入图片描述
我们发现,再对指针_finish更新时,不能用size(),因为之前的_start已经进行了更新,计算不到之前的数据个数了。
我们需要在进行更新指针之前,对这个size()进行备份,对于_endofstorage,容量也发生来更新,不能用原来的

		void reserve(size_t n)
		{
			//判断是否需要开空间
			if (n > capacity())
			{
			    //提前记录数据个数
				size_t old = size();
				//开空间
				T* tmp = new T[n];
				//拷贝之前的数据到新空间
				memcpy(tmp, _start, sizeof(T) * old);
				//更新指针
				_start = tmp;
				_finish = _start + old;
				_endofstorage = _start + n;
			}
		}

我们这里还有问题需要处理,memcpy拷贝呢是一个字节一个字节的拷贝,属于浅拷贝。
我们画个图来看一下
在这里插入图片描述
浅拷贝对于没有资源的类型是没有问题的,但是对于有资源的成员变量就有问题了,开辟新空间,拷贝数据,两块空间的内容都指向同一块空间,旧空间需要被释放,指向的那块空间就被释放掉了,新空间就没法玩了。
我们在处理vector中挪动数据的问题是不能用memcpy进行浅拷贝,我们应该开一块同一样的空间,把数据插入进去。

我们这里采取一种全新的方法

	void reserve(size_t n)
	{
		//判断是否需要开空间
		if (n > capacity())
		{
			size_t old = size();
			//开空间
			t* tmp = new t[n];
			if (_start)
			{
				//拷贝之前的数据到新空间
				//memcpy(tmp, _start, sizeof(t) * n);
				for (int i = 0; i < size(); i++)
				{
					tmp[i] = _start[i];
				}
				//释放旧空间
				delete[]_start;
			}
			//更新指针
			_start = tmp;
			_finish = _start + old;
			_endofstorage = _start + n;
		}

tmp[i] = _start[i];就可以完成我们的任务。

void resize(size_t n,const T&val=T( ))

这个函数用于对空间开辟+初始化
我们来看const T&val=T( )这个,这个其实就是缺省值
对于自定义类型会调用它的默认构造,那麽对于内置类型也要调用它的默认构造,这个是为了适应模板才加入的。
比如int默认为0,char默认为’/0‘,double默认为0.0等等
在这里插入图片描述
对于resize的大小,有三种情况:
🌟 size()<n<capacity,我们仅需要插入数据即可
🌟 size()>capacity(),我们先需要开空间,再进行数据的插入
🌟 size()>n,我们需要对数据进行删除
我们可以把前两种情况合并到一起,直接开空间

void resize(size_t n,const T&val=T( ))
{
	//需要开空间
			if (n >size())
		{
			reserve(n);
			while (_finish < _start + n)
			{
				*_finish = val;
				_finish++;
			}
		}
	else
	{
		_finish = _start + n;
	}

}

T& front() 和const T& front()const

获取vector中第一个元素

		T& front()
		{
			return *_start;
		}
		const T& front()const
		{
			return *_start;
		}

T& back()和const T& back() const

获取vector中最后有一个元素

		T& back()
		{
			return *(_finish - 1);
		}
		const T& back() const
		{
			return *(_finish - 1);
		}

void pop_back()

删除最后一个元素

		void pop_back()
		{
			assert(size() > 0);
			_finish--;
		}

iterator insert(iterator pos, const T& val)

在pos位置插入数据val

	void insert(iterator pos, const T& val)
	{
	    //进行断言
		assert(pos >= _start);
		assert(pos <= _finish);
		//先判断扩容
		if (_endofstorage == _finish)
		{
	
			int len = pos - _start;
	        size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
	        reserve(newcapacity);
            pos = _start + len;
		}
		//挪动数据,必须从后往前挪
		iterator end = _finish-1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			end--;
		}
		//插入数据
		*pos = val;
		_finish++;
	}

我们来关注一下这段代码的实现

在这里插入图片描述
如果不加上的话程序会产生错误,就会发生迭代器失效
在这里插入图片描述

当程序发生扩容,原来的空间会进行销毁,vector中三个元素都会发生变化,而pos所处的位置不变,pos的那块空间被销毁,以后进行插入进找不到了。
在解决这类问题时:我们仅仅需要记录一下pos的位置就可以完成操作。

			iterator insert(iterator pos, const T & val)
			{
				assert(pos >= _start);
				assert(pos <= _finish);
				//先判断扩容
				if (_endofstorage == _finish)
				{
					int len = pos - _start;
					size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
					reserve(newcapacity);
					pos = _start + len;
					return pos;
				}

				//挪动数据,必须从后往前挪
				iterator end = _finish-1;
				while (end >= pos)
				{
					*(end + 1) = *end;
					end--;
				}
				//插入数据
				*pos = val;
				_finish++;
				
			}

iterator erase(iterator pos)

删除pos位置的元素,erase也可能会引发迭代器失效

			void erase(iterator pos)
			{
			    //断言检查
				assert(pos >= _start);
				assert(pos < _finish);
				//有、数据移动
				iterator it = pos + 1;
				while (it < _finish)
				{
					*(it+1= *it;
					it++;
				}
				//删除数据
				_finish--;
			}

我们来看一下这段代码

		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		print(v);

		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
				 v.erase(it);
			else {
				++it;
			}
		}
	}
	

这段代码其实是有问题的,如果erase发生缩容,pos是一个迭代器,位置就会发生变化,
有的编译器下size()减小就会发生缩容,那么空间就会发生变化,外边pos位置的值还是指向那块旧空间,我们再继续使用的话就会发生问题
我们画个图来看一下
在这里插入图片描述
我们在使用前,对迭代器进行重新赋值就可以


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

insert和erase形参pos都可能迭代器失效
原则是insert和erase过的迭代器不要进行使用。

迭代器的作用就是让算法不关心底层数据结构,但是底层迭代器就是一个指针,或者是对指针进行一定程度的封装。

比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃。

如果我们想要删除vector中的所有偶数,下面代码是否有问题??

int main()
{
 vector<int> v{ 1, 2, 3, 4 };
 auto it = v.begin();
 while (it != v.end())
 {
 if (*it % 2 == 0)
 v.erase(it);
 ++it;
 }
 
 return 0;
}

这也会造成迭代器失效的问题,我们需要进行修改

int main()
{
 vector<int> v{ 1, 2, 3, 4 };
 auto it = v.begin();
 while (it != v.end())
 {
 if (*it % 2 == 0)
 it = v.erase(it);
 else
 ++it;
 }
 return 0;
}

vector(InputIterator first, InputIterator last)

一段迭代器区间初始化

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

指向连续物理空间的指针就是天然迭代器

        int a[] = { 100, 200, 300 };
		vector<int> v4(a, a+3);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;

完整代码

模板不能声明和定义在同一个文件中

namespace peng
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		//初始化列表初始化
		vector()
		{	}
		size_t size()
		{
			return _finish - _start;
		}
		size_t capacity()
		{
			return _endofstorage - _start;
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		~vector()
		{
			if (_start)
			{
				delete[]_start;
				_start = _finish = _endofstorage = nullptr;
			}
		}
		//拷贝构造
		vector<T>(const vector<T>& v)
		{
			//开空间+尾插
			reserve(capacity());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

		//常见错误
		//void reserve(size_t n)
		//{
		//	//判断是否需要开空间
		//	if (n > capacity())
		//	{
		//		//开空间
		//		T* tmp = new T[n];
		//		if (_start)
		//		{
		//			//拷贝之前的数据到新空间
		//			memcpy(tmp, _start, sizeof(T) * n);
		//			//释放旧空间
		//			delete[]_start;
		//		}
		//		//更新指针

		//		_start = tmp;
		//		_finish = _start +size();
		//		_endofstorage = _start + capacity();
		//	}
		//}
		//void reserve(size_t n)
		//{
		//	//判断是否需要开空间
		//	if (n > capacity())
		//	{
		//		size_t old = size();
		//		//开空间
		//		T* tmp = new T[n];
		//		if (_start)
		//		{
		//			//拷贝之前的数据到新空间
		//			memcpy(tmp, _start, sizeof(T) * n);
		//			//释放旧空间
		//			delete[]_start;
		//		}
		//		//更新指针
		//		_start = tmp;
		//		_finish = _start + old;
		//		_endofstorage = _start + n;
		//	}
		//}

		void reserve(size_t n)
		{
			//判断是否需要开空间
			if (n > capacity())
			{
				size_t old = size();
				//开空间
				T* tmp = new T[n];
				if (_start)
				{
					//拷贝之前的数据到新空间
					//memcpy(tmp, _start, sizeof(t) * n);
					for (size_t i = 0; i < size(); i++)
					{
						tmp[i] = _start[i];
					}
					//释放旧空间
					delete[]_start;
				}
				//更新指针
				_start = tmp;
				_finish = _start + old;
				_endofstorage = _start + n;
			}
		}
			void  push_back(const T & val)
			{
				//插入之前判断扩容
				if (_finish == _endofstorage)
				{
					//计算开多大空间
					size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
					reserve(newcapacity);
				}
				//插入数据
				*_finish = val;
				_finish++;
			}
			///*	vector<T>& operator=(vector<T> v)
			//	{
			//		swap(v);
			//		return *this;
			//	}*/
			T& operator[](size_t pos)
			{
				return _start[pos];
			}
			const T& operator[](size_t pos)const
			{
				return _start[pos];
			}
			void resize(size_t n, const T & val = T())
			{
				//需要开空间
				if (n > size())
				{
					size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
					reserve(newcapacity);
					//插入数据
					while (_finish < _start + n)
					{
						*_finish = val;
						*_finish++;
					}
				}
				else
				{
					_finish = _start + n;
				}
			}
			T& front()
			{
				return *_start;
			}
			T& back()
			{
				return *(_finish - 1);
			}
			const T& front()const
			{
				return *_start;
			}
			const T& back() const
			{
				return *(_finish - 1);
			}

			void pop_back()
			{
				assert(size() > 0);
				_finish--;
			}

			iterator insert(iterator pos, const T & val)
			{
				assert(pos >= _start);
				assert(pos <= _finish);
				//先判断扩容
				if (_endofstorage == _finish)
				{
					int len = pos - _start;
					size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
					reserve(newcapacity);
					pos = _start + len;
					return pos;
				}

				//挪动数据,必须从后往前挪
				iterator end = _finish-1;
				while (end >= pos)
				{
					*(end + 1) = *end;
					end--;
				}
				//插入数据
				*pos = val;
				_finish++;
				
			}
			vector<T>& operator=(vector<T> v)
			{
				swap(v);
				return *this;
			}

			//vector<T>& operator=(vector<T> v)
			//{
			//	//开空间+数据插入
			//	reserve(capacity());
			//	_start = new T[v.capacity()]; //这里要开辟容量的空间大小
			//	_finish = _start + v.size(); //指定里面存到的数据位置
			//	_endofstorage = _start + v.capacity();

			//	memcpy(_start, v._start, v.size() * sizeof(T));  //注意这里有一个Bug
			//	return *this;
			//}


			void swap(vector<T> &v)
			{
				std::swap(_start, v._start);
				std::swap(_finish, v._finish);
				std::swap(_endofstorage, v._endofstorage);
			}
			//void erase(iterator pos)
			//{
			//	assert(pos >= _start);
			//	assert(pos < _finish);
			//	iterator it = pos + 1;
			//	while (it < _finish)
			//	{
			//		*it = *(it + 1);
			//		it++;
			//	}
			//	_finish--;
			//}


			iterator erase(iterator pos)
			{
				assert(pos >= _start);
				assert(pos < _finish);
				iterator it = pos + 1;
				while (it < _finish)
				{
					*(it-1) = *it;
					it++;
				}
				_finish--;
				return pos;
			}
			//template<class InputIterator>
			//vector(InputIterator first, InputIterator last)
			//{
			//	while (first != last)
			//	{
			//		push_back(*first);
			//		++first;
			//	}
			//}

			vector(size_t n, const T& value = T())
			{
				resize(n,value);
			}
		private:
			T* _start = nullptr;
			T* _finish = nullptr;
			T* _endofstorage = nullptr;
		};
    }

总结

以上就是今天要讲的内容,本文仅仅详细介绍了C++vector的模拟实现,希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lim 鹏哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值