deque属于顺序容器的一种,是一个双端队列,它允许在容器的两端进行快速插入和删除元素的操作。

deque虽然支持像vector一样样的按索引访问,但其随机访问效率不如vector。

std::deque适用于需要在首尾两端频繁进行插入和删除的场景。

deque的底层实现

deque的元素不连续存储,而是由多个固定大小的连续内存块组成,并通过一个主控map来管理。

_M_map是一个指针数组,其中每个元素(node)指向一块内存块。内存块才是deque 的储存空间主体。

deque:

  • map:记录每个内存块的地址。
  • map_size:内存块的个数。
  • start迭代器:记录着map数组中首个内存块的信息。
  • finish迭代器:记录着map数组中最后一个内存块的信息。
template<typename _Tp> class _Deque_base {
    typedef _Deque_iterator<_Tp> iterator;
protected:
    _Tp** _M_map;
    size_t _M_map_size;
    iterator _M_start;
    iterator _M_finish;
};

迭代器:

  • cur:指向当前迭代器所代表的元素。
  • first:指向当前数据块的起始位置。
  • last:指向当前数据块的末尾位置。
  • node:指向map数组中存储的指向当前数据块的位置。
template<class _Tp> struct _Deque_iterator {
    typedef _Tp** _Map_pointer;

    _Tp* _M_cur;
    _Tp* _M_first;
    _Tp* _M_last;
    _Map_pointer _M_node;
};

初始状态

初始时,_M_map成员为nullptr , _M_map_size为0。

插入元素

以尾部插入为例:

  1. 首先,deque 检查 _M_finish._M_cur 是否等于 _M_finish._M_last。如果相等,说明当前数据块已满。
  2. 如果数据块未满:新元素直接添加到 _M_finish._M_cur 指向的位置,然后将 _M_finish._M_cur 向后移动一位。
  3. 如果数据块已满:
    • deque 需要移动到下一个数据块。它通过 _M_finish._M_node 指针向后移动一位,找到 _M_map 中下一个可用的指针。
    • 如果 _M_map 中有空闲块,就直接使用。如果没有,deque 会分配一个新块。
    • 然后,将新元素插入到新块的开头,并更新 _M_finish 迭代器的 _M_node 和 _M_cur 指针。

当_M_map满了后,会对其扩容。

头部插入与尾部插入类似,但操作的是_M_start,并且头部插入是从后往前插入。

删除元素

以尾部删除为例:

  • 首先,将 _M_finish._M_cur 指针向前移动一位,使其指向最后一个元素。
  • 如果 _M_finish._M_cur 现在等于 _M_finish._M_first,这意味着当前数据块中的元素已被全部删除。
  • 在这种情况下,deque 会释放这个数据块,并将 _M_finish 迭代器的 _M_node 指针向前移动,指向 _M_map 中的前一个数据块。

随机访问

对于dq[i]:

  1. 计算 _M_start 迭代器的块内偏移

    start_offset_in_block = _M_start._M_cur - _M_start._M_first

  2. 计算i相对deque开头的总偏移量

    total_offset = i + start_offset_in_block

  3. 计算数据块索引

    block_index = total_offset / map_size

  4. 定位数据块的指针

    target_map_node = _M_start._M_node + block_index

  5. 计算块内偏移

    offset_in_block = total_offset % block_size

  6. 定位元素的指针

    target_element_ptr = *target_map_node + offset_in_block

deque的size

deque对象大小是固定的。

std::deque<int> dq;
std::cout << sizeof(dq) << std::endl; // 输出80

deque的构造函数

deque的构造函数和vector类似:

deque<int> v1;                                  // 无元素
deque<int> v2 {1, 2, 3, 4, 5};                  // 列表初始化
deque<int> v3(4);                               // 4个元素,初始值为0
deque<int> v4(5, 3);                            // 5个元素,初始值为3
deque<int> v5(v4);                              // 使用另外一个vector拷贝构造
deque<int> v6(std::move(v5));                   // 使用另外一个vector移动构造
deque<int> v7(arr1, arr1+5);                    // 使用指针初始化
deque<int> v8(v4.begin(), v4.end());            // 使用迭代器初始化[左闭,右开)

deque的成员函数

访问

成员函数 函数说明
at(size_type n) 带有边界检查,返回对索引 n 处元素的引用。如果索引越界,则抛出 std::out_of_range 异常。
operator[](size_type n) 返回对索引 n 处元素的引用。不执行边界检查,速度更快。
front() 返回对第一个元素的引用。
back() 返回对最后一个元素的引用。

修改

成员函数 说明
assign(size_type count, const T& value) deque 的内容替换为 count 个值为 value 的元素。
push_front(const T& value) deque 的开头添加一个新元素 value
push_back(const T& value) deque 的末尾添加一个新元素 value
pop_front() 移除 deque 的第一个元素。
pop_back() 移除 deque 的最后一个元素。
insert(iterator position, const T& value) position 前插入一个值为 value 的新元素。
erase(const_iterator position) 移除 position 处的元素。
emplace_front(Args&&... args) deque 开头通过就地构造插入新元素。
emplace_back(Args&&... args) deque 末尾通过就地构造插入新元素。
emplace(const_iterator position, Args&&... args) position 处通过就地构造插入新元素。
clear() 移除所有元素,使 deque 变为空。
resize(size_type count) 改变 deque 的大小为 count
swap(deque& other) 与另一个 deque 交换内容。

容量

成员函数 说明
empty() 如果 deque 中没有元素,则返回 true
size() 返回 deque 中元素的数量。
max_size() 返回 deque 可以容纳的最大元素数量。和系统或库实现相关。
shrink_to_fit() 减少容量以适应其大小。可能会释放未使用的内存。非强制,能否达成依赖于实现。

迭代器

成员函数 说明
begin() 返回指向 deque 第一个元素的迭代器。
end() 返回指向 deque 最后一个元素之后位置的迭代器。
rbegin() 返回指向 deque 最后一个元素的反向迭代器。
rend() 返回指向 deque 第一个元素之前位置的反向迭代器。
cbegin() 返回指向 deque 第一个元素的 const 迭代器。
cend() 返回指向 deque 最后一个元素之后位置的 const 迭代器。
crbegin() 返回指向 deque 最后一个元素的 const 反向迭代器。
crend() 返回指向 deque 第一个元素之前位置的 const 逆向迭代器。

参考