一、入门
1、deque与vector的区别
deque
的迭代器包含以下信息:
- 当前缓冲区指针(
current_buffer
) - 当前元素在缓冲区内的位置(
current
) - 中控器的位置(
map
)
每次移动迭代器时,需检查是否跨越缓冲区边界,必要时跳转到下一个缓冲区
deque
(双端队列)是C++标准库中的序列容器,支持在头部和尾部高效插入/删除元素,同时允许随机访问。
与vector
的主要区别:
- 存储结构:
vector
使用连续内存块,而deque
由多个分段缓冲区组成,逻辑连续但物理非连续 - 操作效率:
deque
在头部插入/删除时间复杂度为O(1),而vector
头部操作需移动所有元素,效率为O(n) - 内存扩展:
vector
扩容时需整体复制,deque
仅需新增缓冲区
2、如何初始化一个deque
(int 类型为例)
deque<int> d1; // 默认构造
deque<int> d2(10, 5); // 10个元素,每个为5
deque<int> d3(d2.begin(), d2.end()); // 范围复制
deque<int> d4(d3); // 拷贝构造
3、deque常用成员函数有哪些?
push_front()
/push_back()
:头尾插入pop_front()
/pop_back()
:头尾删除operator[]
或at()
:随机访问size()
/empty()
:容量查询
4、deque允许随机访问是怎么做到的?性能怎么样?
效率略低于vector
。
原因:deque
的随机访问需通过中控器定位到具体缓冲区,再计算元素在缓冲区内的偏移,多了一层间接寻址;而vector
直接通过连续内存的基地址+偏移量访问,无需额外查找步骤。
a、确定目标缓冲区:假设每个缓冲区存储block_size
个元素,则目标缓冲区在中控器中的索引为:
buffer_index = (n / block_size) + start_buffer_index;
b、确定元素在缓冲区内的偏移
element_offset = n % block_size;
c、 访问元素
value = *(中控器[buffer_index] + element_offset);
二、进阶
1、解释deque
的底层实现原理(中控器的作用)
deque
底层由多个固定大小的缓冲区组成,通过“中控器”(通常是一个指针数组)管理这些缓冲区的地址。
- 中控器维护各缓冲区的起始地址,使得逻辑上呈现连续空间。
- 插入元素时,若当前缓冲区已满,则分配新缓冲区并更新中控器,避免整体扩容
2、在中间位置插入元素时,deque
和list
的性能差异如何?为什么?
list
在已知迭代器位置时,中间插入/删除时间复杂度为O(1),仅需调整指针。deque
的中间插入/删除需移动元素,时间复杂度为O(n)
原因:deque
需保持逻辑连续性,插入点后的元素需整体移动;而list
作为双向链表无需移动数据
3、deque
的迭代器失效场景有哪些?与vector
有何不同?
在中间插入/删除元素:可能导致后续元素的迭代器失效(需移动元素)。vector
在插入/删除元素时,所有后续迭代器均失效;而deque
仅在涉及缓冲区重新分配时影响部分迭代器。
vector
的所有元素存储在单个连续内存块中。当插入/删除元素时:
- 插入导致扩容:会分配更大的内存块,将旧元素整体复制到新内存,此时所有迭代器(包括首尾指针)均失效。
- 删除或中间插入:后续元素需要向前或向后移动,所有指向移动元素的迭代器(包括之后的迭代器)均失效
deque
由多个固定大小的缓冲区组成,通过中控器(指针数组)管理。插入/删除时:
- 头尾插入不触发缓冲区扩容:仅修改中控器的头尾指针,其他迭代器仍有效。
- 头尾插入触发缓冲区扩容:中控器可能需要扩展(例如中控器的指针数组已满),此时所有迭代器可能失效(但实际实现会尽量避免)。
- 中间插入/删除:需移动元素,导致部分迭代器失效,但其他缓冲区的迭代器仍有效。
三、高阶
1、在实际开发中,deque
适合哪些应用场景?举例说明
- 双端操作频繁的场景:如实现滑动窗口算法、任务调度队列
- 需要随机访问的队列:例如需要快速访问历史记录的撤销/重做功能(结合
push_front
和随机访问) - 替代
vector
的中间插入场景:若仅在两端操作,deque
性能优于vector
,且避免内存频繁重分配
2、为何deque
在STL的stack
和queue
中作为默认底层容器?
- 内存效率:
deque
的内存利用率高于list
(无节点指针开销) - 性能平衡:
stack
和queue
仅需操作一端或两端,deque
的O(1)头尾操作和连续内存访问特性更合适 - 历史原因:
vector
曾作为stack
默认容器,但deque
的头部扩展能力更灵活
3、 多线程环境下使用deque
需要注意什么?
- 线程安全性:C++标准库容器本身不保证线程安全,需外部同步(如互斥锁)。
- 操作原子性:例如
push_back()
和pop_front()
需加锁,避免竞争条件
4、如何优化deque
的性能?是否支持自定义内存分配器?
- 预分配缓冲区(如通过构造函数指定初始大小)。
- 避免频繁的中间插入/删除操作。
- 自定义内存分配器:支持。可通过模板参数替换默认分配器,优化内存管理策略