【C++STL之vector类模拟

2023年 9月 30日 76.0k 0

一、前言

大家好,在上一文中,我们重点介绍了 STL中的string类,明白了如何去操作字符串。本文我们将要来介绍的是STL中的vector类

二、vector深度剖析及模拟实现【✔】

在介绍完了【vector】的基本接口后,我们就透过源码来深入理解一下

1、源码引入

  • 以下我所介绍的都是基于【SGI】版本的STL,对源码有兴趣的同学可以去看看 侯捷老师的《STL源码剖析》

在这里插入图片描述

  • 然后呢我们就去调出【vector】的一些核心源码,这里我们主要关注的就是这个使用原生指针value_type*所定义出来的迭代器 iterator
  • 然后我们又看到了保护成员:[start][finish][end_of_stroage]。看到它们你是否有想起我们在 模拟string 的时候写到过的 [a][size][capacity];没错,它们就存在着一定的对应关系

在这里插入图片描述

  • 但是呢,只看上面这些成员变量还不够,我们要将其带入到具体的场景中,例如下面有两个接口分别为【尾插】和【扩容】,对于push_back()封装得没有那么厉害,读者结合下面的图应该就能看得懂,分别就是 未满追加的逻辑和已满扩容的逻辑
  • 那对于reserve()来说,就是一个扩容的逻辑,【allocate_and_copy】是开辟和拷贝空间,那【deallocate】就是释放空间。在扩完容之后不要忘了去对三个成员变量做更新,这一块的模拟实现我在下面马上就会讲到

在这里插入图片描述

  • 最后的话我们再来看看 构造函数construct和析构函数destroy,光看代码,不知你是否回忆起了我们曾经在 C/C++内存管理 中有讲到【定位new】这个概念,而且提到了 内存池 这个概念
  • 其实我们在调用构造函数的时候,都是通过【空间适配器】在 内存池 中开出空间;那在出了作用域之后这些所开的空间都要销毁了,所以就会去调用析构函数完成释放

在这里插入图片描述

💬 对于上面的这些源码呢,读者可以在学习了STL一段时间后,配合侯捷老师的《STL源码剖析》再去展开阅读,因为考虑到读者的基础,就不在继续深入讲解了~

2、模拟实现

然后我们就来模拟实现一下【vector】中的各种接口

  • 还是一下,我们先简述一下整体的架构。这个vector类还是包在【bit】这个命名空间中,而对于这个类而言,我要将其定义为一个 模版类,这一块如果还有同学不太熟悉的话可以去看看 C++模版
  • 其他部分可以看到迭代器我定义的就是原生指针类型,然后将[_start][_finish][_end_of_storage]也定义为了三个迭代器类型,并且采用提前声明的形式将它们都初始化为nullptr,这样当我们后面在写 构造函数和析构函数 的时候就不需要再去做初始化了
namespace bit {
	template
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	// 主要接口函数
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

1)迭代器

  • 首先的话简单一点,来实现一下迭代器,分为非const版本const版本
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin()  const
{
	return _start;
}

const_iterator end()	const
{
	return _finish;
}

2)容量

  • 然后我们来讲讲容量相关的接口,首先的话就是【size】和【capacity】这两个接口
size_t size()
{
	return _finish - _start;
}

size_t capacity()
{
	return _end_of_storage - _start;
}
  • 对于【size】而言指的是当前这个容器中的数据个数,那也就是我们在上面所讲的_start_finish这两个迭代器之间的距离,我们之前有说到过迭代器它的底层其实就是指针,那要计算出两个指针之间的数据个数的话让它们做一个相减_finish - _start
  • 那对于整个容器的容量【capacity】来说,即为_end_of_storage - _start。读者通过下图便可一目了然地看出来

在这里插入图片描述

  • 然后呢再来说说扩容这一块的接口【reserve】,首先在一进来的时候我们要去做一个判断,只有当所传入的值要比原先的capacity()来得大的时候,我们才去执行一个扩容的逻辑,在内部的扩容逻辑中可以看到我们使用到了前面所定义的模版参数T,这样去写的话就可以根据不同的类型参数开出不同的空间
  • 接下去我们所执行的就是拷贝的逻辑,采取到的是内存函数memcpy(),拷贝完后再去释放原空间,接下去把这些成员变量去做一个更新即可

==看着逻辑很清晰,但是呢下面的代码存在着非常多的漏洞==

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];		// 开一块新空间
		if (_start)
		{
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + size();
		_end_of_storage = _start + n;
	}
}
  • 我们这里再写一个push_back的接口(后面讲),让代码先跑起来
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newCapacity);
	}
	*_finish = x;
	_finish++;
}

💻第一轮测试 — 空指针异常

下面是测试的代码

void test_vector1()
{
bit::vector v;

v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

for (auto e : v)
{
cout

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论