C++ STL 容器 deque

本文详细解析了C++deque容器的内部结构,包括其分段连续的内存管理机制、迭代器的实现以及与vector的重新分配区别。重点介绍了deque如何通过映射表支持高效的头部和尾部操作。
摘要由CSDN通过智能技术生成


本文测试环境 gcc 13.1

1. deque 对象

1.1 内部表示

deque 为我们提供了一个双端队列,即可以在队头进行 push、pop,可以在队尾进行 push、pop

deque 容器擅长的就是在头部和尾部添加或删除元素

vector 是一段连续的内存空间,当需要重新分配时,转移到了另一段连续的内存空间,而 deque 的实现比较复杂,deque 底层维护的是一段段连续的内存空间

首先看看 deque<T> 对象的内部表示
在这里插入图片描述
因为需要维护一段段的连续空间,里面保存了很多指针,其中的 iterator 是 deque 的迭代器
sizeof(deque<int>) = 80,相比 vector 大很多

1.2 底层数据结构

下图是具体的模型
在这里插入图片描述
假设现在有一个 deque 对象保存了 1, 2, …, n, n+1, n+2 这样一个序列,那么它们可能就像如图所示的分布情况

左边是一个映射表,表中每一个指针指向一段连续的内存空间,通过这个表来将所有分段联系起来

右边是两段连续的内存空间,用来存放实际的元素,每一段空间所能容纳的元素个数是相同的。图中 0 ~ n 存放在一块,n + 1 存放在另一块,整个序列不是连续的,而是分段连续的

再看看 iterator ,node 指向映射表中的一个数据,来定位这段空间,first 和 last 分别指向这段空间的首地址和可用空间的末尾地址,iterator start 的 cur 指针指向第一个元素,而 iterator finish 的 cur 指针指向的是最后一个元素的下一个位置

1.3 分段连续

iterator 中 node 的作用就很关键了,想从一段空间跳到下一段空间,就需要将 node + 1 ,这样 node 指向了下一段内存空间,迭代器就可以访问下一段空间里的元素了,这就是 deque 实现的分段连续

底层 deque 申请了一段段的空间,通过这个 map 映射表,让用户感觉就是一段完整的连续空间

当头部或尾部要超出容量时,就需要分配新的一段空间,建立一个新的映射关系,在新的一段空间里构造新元素

1.在头部添加元素:
在这里插入图片描述
如图,现在想 push_front(0) 在头部添加一个元素,因为再添加已经超出空间,所以需要分配一个新的空间,添加之后情况如下
在这里插入图片描述
新添加的 0 在新空间的末尾

2.在末尾添加元素:
在这里插入图片描述
如图,现在想 push_back(n+1) 在末尾添加一个元素,但是现在的空间的元素个数已经用完了,需要分配一个新空间,添加之后情况如下
在这里插入图片描述
新添加的 n + 1 在新空间的首部

1.4 重新分配

vector 的重新分配需要将元素从旧空间转移至新空间

而 deque 的重新分配是指 map 映射表用完了,需要申请新空间容纳更多的映射关系,而实际存放元素的那些空间并不需要处理,只需要将旧映射表中的指针拷贝到新映射表就行了

2. deque 迭代器

deque 对象中已经使用了 start 和 finish 来定位首尾元素了,下面我们看看 deque 迭代器的具体实现

template<typename _Tp, typename _Ref, typename _Ptr>
struct _Deque_iterator
{
	_Elt_pointer _M_cur;	// T*
	_Elt_pointer _M_first;
	_Elt_pointer _M_last;
	_Map_pointer _M_node;	// T**
}

deque 迭代器实现为随机访问迭代器

cur 指针指向的就是具体的元素,通过 cur 就能访问元素了

 _GLIBCXX_NODISCARD
reference
operator*() const _GLIBCXX_NOEXCEPT
{ return *_M_cur; }

_GLIBCXX_NODISCARD
pointer
operator->() const _GLIBCXX_NOEXCEPT
{ return _M_cur; }

++ – 也就是 cur 指针移动,这里就是要考虑 cur 到达 fisrt 或 last 需要跳转到另一段空间中,这就需要改变 node 了,同时修改 first 和 last ,如果 cur 需要跳转到前一段空间,那么 cur 应该指向这段空间的末尾也就是 last - 1,如果是跳转下一段空间,那么 cur 应指向这段空间的首地址也就是 first

_Self&
operator++() _GLIBCXX_NOEXCEPT
{
	++_M_cur;
	if (_M_cur == _M_last)
	{
		_M_set_node(_M_node + 1);
		_M_cur = _M_first;
	}
	return *this;
}

_Self&
operator--() _GLIBCXX_NOEXCEPT
{
	if (_M_cur == _M_first)
	{
		_M_set_node(_M_node - 1);
		_M_cur = _M_last;
	}
	--_M_cur;
	return *this;
}

跳转到另一个空间,修改 node

void
_M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT
{
	_M_node = __new_node;
	_M_first = *__new_node;
	_M_last = _M_first + difference_type(_S_buffer_size());
}

这样就可以在分段的空间上遍历所有元素了

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值