【C++】 初阶篇 STL vector的模拟实现

一、浅析 vector 源码

对于编程来说,学习初期进步最快的方式就是阅读别人优秀的代码,理解其中的逻辑和细节后自己独立的去实现几次,学习 STL 也是如此;我们可以适当的去阅读 STL 的源码,当然我们并不是要逐行的进行阅读,因为这样太耗费时间,况且其中很多 C++ 的语法我们也还没学。

当前阶段,我们阅读 STL 源码是为了学习 STL 库的核心框架,然后根据这个框架自己模拟实现一个简易的 vector (只实现核心接口);阅读源码与模拟实现能够让我们更好的了解底层,对 STL 做到 能用,并且 明理。
在这里插入图片描述
在这里插入图片描述
可以看到,vector.h 仅仅是将几个头文件包含在一起,vector 的主要实现都在 stl_vector.h 里面。

二、核心框架

我们可以根据上面的 vector 源码来得出 vector 的核心框架:

namespace ljm {
  template<class T>
  class vector 
  {
  public:
      typedef T* iterator;
      typedef const T* const_iterator;

  public:
      //成员函数

  private:
      T* _start;
      T* _finish;
      T* _end_of_storage;
  };
}

可以看到,vector 的底层和 string 一样,都是一个指针指向一块动态开辟的数组,但是二者不同的是,string 是用 _size 和 _capacity 两个 size_t 的成员函数来维护这块空间,而 vector 是用 _finish 和 _end_of_storage 两个指针来维护这块空间;虽然 vector 使用指针看起来难了一些,但本质上其实是一样的 _size = _finish - _start, _capacity = _end_of_storage - _start 在这里插入图片描述

三、构造函数

3.1、构造函数实现

//无参构造
vector()
  :_start(nullptr)
  ,_finish(nullptr)
  ,_endofstorage(nullptr)
{
}

// 填充构造:创建n个值为val的元素
vector(size_t n, const T& val = T())
  :_start(nullptr)
  , _finish(nullptr)
  , _endofstorage(nullptr)
{
  _start = new T[n];
  _finish = _start;
  _endofstorage = _start + n;
  while (n-- && n != 0)
  {
  	*_finish = val;
  	_finish++;
  }
}

// 范围构造:通过其他容器/序列的迭代器范围[first,last)初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
  :_start(nullptr)
  , _finish(nullptr)
  , _endofstorage(nullptr)
{
  size_t n = last - first;//这里计算链式存储结构的时候会有问题,n不对,我后续可以通过push-back来进行
  _start = new T[n];
  _finish = _start;
  _endofstorage = _start + n;
  while (first != last)
  {
  	*_finish=*first;
  	_finish++;
  	first++;
  }
}

3.2、问题

在我们模拟实现了构造函数中的迭代器区间构造和 n 个 val 构造后,我们会发现一个奇怪的问题,我们使用 n 个 val 来构造其他类型的对象都没问题,唯独构造 int 类型的对象时会编译出错,如下:
在这里插入图片描述

这是由于编译器在进行模板实例化以及函数参数匹配时会调用最匹配的一个函数,当我们将 T 实例化为 int 之后,由于两个参数都是 int,他就走了迭代器构造,所以对于迭代器构造函数来说,它会直接将 InputIterator 实例化为 int;

但对于 n 个 val 的构造来说,它不仅需要将 T 实例化为 int,还需要将第一个参数隐式转换为 size_t;所以编译器默认会调用迭代器构造,同时由于迭代器构造内部会对 first 进行解引用,所以这里报错 “非法的间接寻址”;

这个时候有两种办法解决这个问题:

方法1:

将6显式指定为size_t类型(写成10u),这样编译器会优先匹配填充构造函数(而非范围构造函数),从而正常创建包含 10 个值为 1 的元素的 vector。
在这里插入图片描述

方法2:

新增一个以int类型为参数的填充构造函数重载版本,让vector v(10, 1)这类传入int型数值的场景,能直接匹配该重载函数,避免编译器误匹配范围构造函数。
在这里插入图片描述

四、拷贝构造和赋值运算符重载(深拷贝)

由于vector包含动态申请的资源,因此不能使用浅拷贝(浅拷贝仅复制指针地址,会导致多个对象共用同一块内存,引发析构时重复释放等问题),必须实现深拷贝:

  	//swap
  	void swap(vector<T>& v)
  	{
  		std::swap(_start, v._start);  //复用算法库的swap函数
  		std::swap(_finish, v._finish);
  		std::swap(_endofstorage, v._end_of_storage);
  	}

  	//拷贝构造传统写法
  	//vector(const vector<T>& v)
  	//	:_start(nullptr)
  	//	, _finish(nullptr)
  	//	, _endofstorage(nullptr)
  	//{
  	//	size_t n = v._start -v. _finish;//这里计算链式存储结构的时候会有问题,n不对,我后续回来实现那一个方法的。
  	//	_start = new T[n];

  	//	//浅拷贝 - 容器存储的是有动态资源的自定义类型,使用memcpy就会造成浅拷贝
  	//	// 当 vector 里存的是带有动态资源的类(比如含 char* 成员),memcpy 会把同一块堆地址复制给两个对象,结果双份析构 → 同一块内存被 free 两次 → 程序崩溃。
  	//	//memcpy(_start,v._start,sizeof(T)* v.size());

  	//	//深拷贝
  	//	for (size_t i = 0; i < v.size(); i++)
  	//	{
  	//		_start[i] = v._start[i];
  	//	}
  	//	_finish = _start + v.size();
  	//	_endofstorage = _start + v.capacity();
  	//}


  	//拷贝构造现代写法
  	vector(const vector<T>& v)
  		:_start(nullptr)
  		, _finish(nullptr)
  		, _endofstorage(nullptr)
  	{
  		vector<T> tmp(v.begin(), v.end());  //复用构造函数和swap函数
  		swap(tmp);//空手套白狼
  	}

  	///赋值重载传统写法
  	//vector<T>& operator=(vector<T>& v)
  	//{
  	//	T* tmp = new T[v.capacity()];
  	//	for (size_t i = 0; i < v.size(); i++)
  	//	{
  	//		tmp[i] = v._start[i];
  	//	}
  	//	delete[] _start;

  	//	_start = tmp;
  	//	_finish = _start + v.size();
  	//	_endofstorage = _start + v.capacity();

  	//	return *this;
  	//}

  	//赋值重载现代
  	// v1 = v2
  	vector<T>& operator=(vector<T> v)
  	{
  		swap(v);

  		return *this;
  	}

五、析构函数

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

六、迭代器

由上面可得

iterator begin()
{
  return _start;
}

iterator end()
{
  return _finish;
}

const_iterator begin() const
{
  return _start;
}

const_iterator end() const
{
  return _finish;
}

七、vector容量相关接口

7.1、size

size_t size()
{
  return _finish - _start;
}

7.2、capacity

  size_t capacity()
  {
  	return _endofstorage - _start;
  }

7.3、empty

  	bool empty()
  	{
  		return size() == 0;
  	}

7.4、reserve

  	void reserve(size_t n)
  	{

  		if (n > capacity())
  		{
  			T* tmp = new T[n];//开空间
  			size_t sz = size();
  			if (_start)
  			{
  				for (size_t i = 0; i < sz; i++)//拷数据
  				{
  					tmp[i] = _start[i];
  				}
  				delete[] _start;
  			}
  			_start = tmp;
  			_finish = _start + sz;
  			_endofstorage = _start + n;				
  		}
  		else
  		{
  			return;
  		}
  		
  	}

该接口用于为vector预留至少n个元素的存储空间,仅当n大于当前容器容量时才执行扩容操作;扩容时先记录当前有效元素个数,开辟n个大小的临时空间,通过循环调用元素赋值重载完成深拷贝(解决自定义类型动态资源的浅拷贝问题),随后释放原空间,更新容器的_start、_finish、_endofstorage指针,使容器容量更新为n,且保留原有全部有效元素,有效避免频繁扩容带来的性能损耗。

扩容接口有两个特殊问题:

1、自定义类型动态资源的浅拷贝问题:当容器存储的是带动态资源的自定义类型时,直接用memcpy拷贝会导致浅拷贝,这一问题的解决思路在赋值运算符重载和拷贝构造的内容中已有说明,此处不再赘述;

2、必须提前用 sz 记录当前有效元素个数:因为扩容后原空间会被释放,_start指针会指向新空间,若此时再调用size()获取元素个数,会因_start和_finish不再指向原空间而计算错误,所以需要在扩容前先把当前元素个数存到 sz 变量中。

7.5、resize

先看是什么样的

 ////1.n<capacity()&&n<size,不用扩容,也不用加数据
 vector<int> v1(5,6);
 for (auto v:v1)
 {
     cout << v;
 }
 cout << endl;
 cout << "1.开始:" << v1.size() << " " << v1.capacity() << endl;
 v1.resize(2, 2);
 for (auto v : v1)
 {
     cout << v;
 }
 cout << endl;
 cout << "1.结束:" << v1.size() << " " << v1.capacity() << endl;
 //2.n<capacity()&&n>size,不用扩容,加数据
 v1.resize(5, 5);
 for (auto v : v1)
 {
     cout << v;
 }
 cout << endl;
 cout << "1.结束:" << v1.size() << " " << v1.capacity() << endl;

 v1.resize(10, 2);
 for (auto v : v1)
 {
     cout << v;
 }
 cout << endl;
 cout << "1.结束:" << v1.size() << " " << v1.capacity() << endl;

在这里插入图片描述

这段resize接口用于调整vector的有效元素个数:

第二个参数的缺省值是调用默认构造,内置类型由于加入了模板,也有了自己的默认构造

当 n 小于当前元素数时,直接截断(仅调整_finish指针,保留前 n 个元素);

当 n 更大时,先调用reserve预留空间,再将新增位置填充为指定值,最终更新_finish指针,既支持缩小容器,也能安全扩容并填充元素。

  	void resize(size_t n, const T& val = T())
  	{
  		if (n < size())
  		{
  			_finish = _start + n;  // 缩小,析构多余元素(如有需要)
  		}
  		else if (n > size())
  		{
  			reserve(n);  // reserve 内部会判断是否需要扩容

  			size_t old_size = size();
  			for (size_t i = old_size; i < n; i++)
  			{
  				_start[i] = val; 
  			}
  			_finish = _start + n;
  		}
  	}

八、vector增删查改

8.1、push_back

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

  		}
  		*_finish = x;
  		_finish++;
  		
  	}

8. 2、insert

  //指定位置插入
  iterator insert(iterator pos, const T& x)
  {
  	if (_finish == _endofstorage)
  	{
  		//记录相对位置
  		size_t len = pos - _start;

  		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
  		reserve(newcapacity);

  		//之前的pos位置失效了之前的是基于没扩容前的
  		pos = _start + len;
  	}
  	iterator end = _finish ;
  	while (pos <= end)
  	{
  		*end  = *(end-1);
  		end--;
  	}
  	*pos = x;
  	_finish++;
  	return pos;
  }

8.3、erase

// 指定位置删除元素
iterator erase(iterator pos)
{
  // 断言:确保pos迭代器合法(在有效元素区间内),且vector非空,防止非法访问
  assert(pos >= _start && pos < _finish && size()>0);

  // 定义迭代器it,从pos的下一个元素开始遍历
  iterator it = pos + 1;
  // 循环:将pos之后的所有元素依次向前移动一位,覆盖待删除元素(实现逻辑删除)
  while(it != _finish)
  {
      *(it - 1) = *it;  // 后一个元素的值赋给前一个元素,完成前移覆盖
      it++;             // 迭代器后移,处理下一个元素
  }

  // 尾指针前移一位,缩小有效元素范围(底层内存未释放,仅标记该位置元素无效)
  --_finish;

  // 返回原pos迭代器(此时pos指向被删除元素的下一个有效元素,避免外部迭代器失效)
  return pos;
}

8.4、pop_back

//尾删
void pop_back()
{
  earse(--end());
}

8.5、operator[]

// 可读可写版本:返回非const引用,支持修改元素值
  	T& operator[](size_t pos)
  	{
  		assert(pos < size()); // 断言确保访问的下标合法,不越界

  		return _start[pos]; // 返回对应位置元素的引用,支持读和写
  	}

  	// 仅读版本:const成员函数,返回const引用,禁止修改元素值
  	const T& operator[](size_t pos) const
  	{
  		assert(pos < size()); // 断言确保访问的下标合法,不越界

  		return _start[pos]; // 返回对应位置元素的const引用,仅支持读操作
  	}

8.6、find

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val)
{
  while (first != last)
  {
  	if (*first == val)
  	{
  		return first;
  	}
        
      first++;
  }

  return last;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值