前言
在 C++ 标准模板库(STL)中,std::list
和 std::deque
是两种重要的顺序容器。本文将对这两种容器进行全面分析,探讨它们的内存分布、底层原理、性能特点、适用场景以及常见使用方法,并对比迭代器在不同操作中的失效情况。
std::deque
内存分布
std::deque
(双端队列)由多个固定大小的连续内存块构成,这些内存块可以分布在内存中的不同位置。
底层原理
std::deque
的实现通常基于一个节点数组,每个节点包含数据和指向前后节点的指针,形成循环链表结构。
增加元素性能
- 尾部添加:
push_back()
通常为 O(1)。 - 头部添加:
push_front()
通常为 O(1)。 - 中间添加:可能需要 O(n),因为要移动元素。
删除元素性能
- 尾部删除:
pop_back()
通常为 O(1)。 - 头部删除:
pop_front()
通常为 O(1)。 - 中间删除:可能需要 O(n),因为要移动元素。
元素访问性能
提供随机访问能力,访问时间复杂度为 O(1)。
适用场景
适用于需要快速在两端进行元素插入和删除的场景。
常见方法
push_back(T value)
push_front(T value)
pop_back()
pop_front()
insert(Iterator pos, T value)
erase(Iterator pos)
应用示例
#include <deque>
#include <iostream>
int main() {
// 创建一个整数类型的双端队列
std::deque<int> dq;
// 使用 push_back 在尾部添加元素
dq.push_back(10);
dq.push_back(20);
std::cout << "After push_back: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 push_front 在头部添加元素
dq.push_front(5);
std::cout << "After push_front: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 pop_back 删除尾部元素
dq.pop_back();
std::cout << "After pop_back: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 pop_front 删除头部元素
dq.pop_front();
std::cout << "After pop_front: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
// 在特定位置插入元素
auto it = dq.begin() + 1; // 获取迭代器
dq.insert(it, 15); // 在迭代器指向的位置插入元素
std::cout << "After insert: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
// 删除特定位置的元素
it = dq.begin(); // 获取迭代器
dq.erase(it); // 删除迭代器指向的元素
std::cout << "After erase: ";
for (const auto& value : dq) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
迭代器失效情况
- 尾部添加:不会导致迭代器失效。
- 头部添加:不会导致迭代器失效。
- 中间添加:插入点之后的迭代器失效。
- 尾部删除:不会导致迭代器失效。
- 头部删除:不会导致迭代器失效。
- 中间删除:删除点之后的迭代器失效。
小结:
1.deque底层时固定大小的连续内存块的链接而成,因此即具备连续存储特性又有链式存储特性
2.deque在插入元素,删除元素时,首尾位置常数时间复杂度,中间位置O(n)
3.deque在元素访问上,同样是O(1),即支持随机访问
4.deque在中间位置进行元素的插入或者删除时都会导致插入点或删除点之后的迭代器失效。
std::list
内存分布
std::list
中的元素在内存中是分散分布的,每个元素是一个包含数据和两个指针的节点。
底层原理
基于双向链表,每个节点包含指向前一个和后一个节点的指针。
增加元素性能
- 尾部添加:
push_back()
为 O(1)。 - 头部添加:
push_front()
为 O(1)。 - 中间添加:在任意位置添加元素为 O(1)。
删除元素性能
- 尾部删除:
pop_back()
为 O(1),需要尾部迭代器。 - 头部删除:
pop_front()
为 O(1)。 - 中间删除:在任意位置删除元素为 O(1)。
元素访问性能
不支持随机访问,访问特定位置的元素需要 O(n)。
适用场景
适用于需要频繁在任意位置进行元素插入和删除的场景。
常见方法
push_back(T value)
push_front(T value)
pop_back()
pop_front()
insert(Iterator pos, T value)
erase(Iterator pos)
clear()
应用示例
#include <list>
#include <iostream>
int main() {
std::list<int> lst;
// 使用 push_back 在尾部添加元素
lst.push_back(10);
lst.push_back(20);
std::cout << "After push_back: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 push_front 在头部添加元素
lst.push_front(5);
std::cout << "After push_front: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 pop_back 删除尾部元素
if (!lst.empty()) {
lst.pop_back();
}
std::cout << "After pop_back: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用 pop_front 删除头部元素
if (!lst.empty()) {
lst.pop_front();
}
std::cout << "After pop_front: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 在特定位置插入元素
auto it = lst.begin(); // 获取迭代器
++it; // 移动到第二个元素
lst.insert(it, 15); // 在迭代器指向的位置插入元素
std::cout << "After insert: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 删除特定位置的元素
it = lst.begin(); // 获取迭代器
++it; // 移动到第二个元素
lst.erase(it); // 删除迭代器指向的元素
std::cout << "After erase: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
// 清空整个链表
lst.clear();
std::cout << "After clear: ";
for (const auto& value : lst) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
迭代器失效情况
- 尾部添加:不会导致迭代器失效。
- 头部添加:不会导致迭代器失效。
- 中间添加:插入点之前的迭代器失效。
- 尾部删除:删除点之后的迭代器失效。
- 头部删除:不会导致迭代器失效。
- 中间删除:删除点之后的迭代器失效。
小结
1.list不要求内存是连续的,各个节点可以在内存的各个位置;
2.list的元素插入,删除性能是O(1),但是不支持随机访问,只能按序遍历
3.添加元素,中部前失效;删除元素,中尾后失效
对比总结
特性/操作 | std::deque | std::list |
---|---|---|
内存布局 | 多个连续内存块 | 双向链表,元素分散 |
随机访问 | 支持,可能稍慢 | 不支持,需要 O(n) 时间遍历 |
尾部添加 | 高效,通常 O(1) | 高效,通常 O(1) |
头部添加 | 高效,通常 O(1) | 高效,通常 O(1) |
中间添加 | 可能低效,可能需要 O(n) | 高效,通常 O(1) |
尾部删除 | 高效,通常 O(1) | 高效,如果已知尾部迭代器 |
头部删除 | 高效,通常 O(1) | 高效,通常 O(1) |
中间删除 | 可能低效,可能需要 O(n) | 高效,通常 O(1) |
适用场景 | 两端操作频繁 | 任意位置插入和删除频繁 |
迭代器失效情况 | 中间操作可能失效 | 中间操作,插入点之前迭代器失效 |