引言
C++ STL 中提供了多种容器,它们在功能和性能上各有特点,适用于不同场景。主要的容器类型分为以下三大类:
1. 顺序容器
顺序容器按插入顺序存储元素,适合频繁的插入、删除和遍历操作。
-
std::vector (动态数组):是一个动态数组,在内存中以连续空间存储元素,支持快速随机访问。适用于需要动态扩展、支持随机访问,但不频繁在中间插入和删除的场景。
-
std::deque(双端队列):支持高效的头尾插入和删除操作,与
vector类似,提供动态数组的功能,但不保证连续内存空间。适合需要在头尾频繁插入和删除的场景。 -
std::list(双向链表):采用双向链表实现,支持快速的插入和删除操作,但不支持随机访问。适合需要频繁在容器中间插入和删除的场景。
-
std::forward_list(单向链表):单链表,只能向前遍历,比
list更节省空间。适合内存资源有限,且只需要单向遍历的场景。 -
std::array(静态数组):
array是 C++11 引入的一个定长数组包装类,大小在编译期确定,无法动态改变。它和普通数组类似,但提供了 STL 的接口。
2. 关联容器
关联容器内部使用平衡二叉树(通常为红黑树)存储元素,能够快速查找、插入和删除,适合对键进行排序和快速检索的场景。
-
std::set:存储唯一的键值元素,元素按升序排列,不支持重复元素。适合需要去重和有序访问的场景。
-
std::multiset:与
set类似,但允许重复元素。 -
std::map:键值对容器,存储的元素是
<key, value>形式,每个键是唯一的,元素按键值升序排列。适合按键快速查找和有序存储的场景。 -
std::multimap:与
map类似,但允许重复键值。
3. 无序关联容器
无序关联容器在内部采用哈希表实现,元素没有顺序,但能提供快速的查找和插入性能(均摊复杂度为 O(1)),适合不需要有序存储的场景。
-
std::unordered_set:存储唯一的键值元素,元素无序,插入和查找的均摊时间复杂度较低。
-
std::unordered_multiset:与
unordered_set类似,但允许重复元素。 -
std::unordered_map:键值对容器,键是唯一的,采用哈希表存储,元素无序。适合键值对查询性能要求较高的场景。
-
std::unordered_multimap:与
unordered_map类似,但允许重复键值。
4. 容器适配器
容器适配器是对底层容器(如 deque 或 list)的一种封装,使其具有特定的行为。
-
std::stack:后进先出(LIFO)的栈结构,只能在栈顶插入和删除元素。通常由
deque实现,也可以由vector或list支持。 -
std::queue:先进先出(FIFO)的队列结构,只能在队列的头部和尾部插入和删除元素。默认使用
deque作为底层容器。 -
std::priority_queue:优先级队列,元素按优先级排序,默认按降序排列(大顶堆)。可使用自定义比较器控制排序方式。
5. 容器总结对比
| 容器 | 底层实现 | 是否有序 | 是否允许重复 | 插入效率 | 查找效率 | 特点 |
|---|---|---|---|---|---|---|
| vector | 动态数组 | 无序 | 允许 | O(1) 尾插 | O(1) 随机访问 | 动态数组,支持快速随机访问和扩展 |
| deque | 双端数组 | 无序 | 允许 | O(1) 头尾插 | O(1) 随机访问 | 双端队列,支持头尾插入 |
| list | 双向链表 | 无序 | 允许 | O(1) 任意位置插入 | O(n) 顺序查找 | 双向链表,适合频繁插入和删除 |
| forward_list | 单向链表 | 无序 | 允许 | O(1) 头部插入 | O(n) 顺序查找 | 单向链表,节省空间 |
| array | 静态数组 | 无序 | 允许 | O(1) 访问 | O(1) 访问 | 定长数组,大小不可变 |
| set | 红黑树 | 有序 | 不允许 | O(log n) 插入 | O(log n) 查找 | 有序集合,不允许重复元素 |
| multiset | 红黑树 | 有序 | 允许 | O(log n) 插入 | O(log n) 查找 | 有序集合,允许重复元素 |
| map | 红黑树 | 有序 | 不允许 | O(log n) 插入 | O(log n) 查找 | 有序键值对,键唯一 |
| multimap | 红黑树 | 有序 | 允许 | O(log n) 插入 | O(log n) 查找 | 有序键值对,键可重复 |
| unordered_set | 哈希表 | 无序 | 不允许 | O(1) 插入(均摊) | O(1) 查找(均摊) | 无序集合,不允许重复元素 |
| unordered_multiset | 哈希表 | 无序 | 允许 | O(1) 插入(均摊) | O(1) 查找(均摊) | 无序集合,允许重复元素 |
| unordered_map | 哈希表 | 无序 | 不允许 | O(1) 插入(均摊) | O(1) 查找(均摊) | 无序键值对,键唯一 |
| unordered_multimap | 哈希表 | 无序 | 允许 | O(1) 插入(均摊) | O(1) 查找(均摊) | 无序键值对,键可重复 |
| stack | 适配器(通常基于 deque) | - | - | O(1) 压入/弹出 | - | LIFO 栈,后进先出 |
| queue | 适配器(通常基于 deque) | - | - | O(1) 入队/出队 | - | FIFO 队列,先进先出 |
| priority_queue | 二叉堆 | - | - | O(log n) 插入 | O(1) 取顶 | 优先级队列,元素按优先级排序 |
C++ STL 提供了丰富的容器选择,可以根据实际场景选择合适的容器,以下是进一步的详细介绍。
一、vector
std::vector 是 C++ 标准模板库(STL)中最常用的容器之一,是一个动态数组,可以根据需要动态调整大小。与普通的数组相比,std::vector 提供了更多的灵活性和功能。以下是对 std::vector 的详细介绍:
1. 基本概念
std::vector 是一个模板类,定义在 <vector> 头文件中。允许存储和操作任意类型的元素,并在底层使用连续的内存布局。std::vector 可以看作是一个动态数组,因此可以在运行时动态扩展和缩小。
#include <vector>
std::vector<int> vec;
2. 主要特性
- 自动扩展:当向
std::vector添加新元素时,如果容量不足,它会自动分配更大的内存空间,并将旧数据复制到新空间中。 - 连续存储:
std::vector在内存中是连续存储的,类似于数组。这使得它与数组一样可以通过指针算术进行快速随机访问。 - 动态调整大小:
std::vector可以在运行时通过resize()、push_back()和pop_back()动态调整大小。 - 内存管理:
std::vector管理自己的内存,通过capacity()可以查看当前容量,通过reserve()可以预先分配空间,以减少后续的扩展开销。
3. 常用操作和方法
3.1 初始化
std::vector 可以通过多种方式初始化,如以下几种方式:
// 默认构造函数
std::vector<int> vec1;
// 指定大小的构造函数,所有元素初始化为默认值
std::vector<int> vec2(10);
// 指定大小和初始值
std::vector<int> vec3(10, 5);
// 使用列表初始化
std::vector<int> vec4 = {1, 2, 3, 4, 5};
// 使用拷贝构造函数
std::vector<int> vec5(vec4);
// 使用迭代器范围初始化
std::vector<int> vec6(vec4.begin(), vec4.end());
3.2 添加和删除元素
push_back(value):在末尾添加一个元素。emplace_back(args...):在末尾原地构造一个元素(避免拷贝或移动操作)。pop_back():删除末尾的元素。insert(position, value):在指定位置插入一个元素。erase(position):删除指定位置的元素。clear():清空所有元素。
示例:
std::vector<int> vec = {1, 2, 3};
vec.push_back(4); // vec: {1, 2, 3, 4}
vec.pop_back(); // vec: {1, 2, 3}
vec.insert(vec.begin(), 0); // vec: {0, 1, 2, 3}
vec.erase(vec.begin()); // vec: {1, 2, 3}
vec.clear(); // vec为空
3.3 访问元素
operator[]:直接通过下标访问元素,不进行边界检查。at(index):通过下标访问元素,进行边界检查。front():返回第一个元素。back():返回最后一个元素。data():返回指向数组首元素的指针,可以与C风格数组兼容。
std::vector<int> vec = {1, 2, 3};
int first = vec.front(); // 1
int last = vec.back(); // 3
int* ptr = vec.data(); // 指向vec的首元素的指针
3.4 大小和容量
size():返回当前元素个数。capacity():返回分配的内存空间可以容纳的元素个数。resize(new_size):调整容器大小,若增大,新元素用默认构造函数初始化。reserve(new_capacity):调整容量,预先分配内存空间以减少后续扩展的开销。shrink_to_fit():将多余的容量释放(不是强制要求,具体实现可能会忽略此请求)。
std::vector<int> vec(5); // vec的size和capacity都是5
vec.reserve(10); // vec的capacity变为10,size不变
vec.resize(3); // vec的size变为3
二、deque
std::deque(双端队列)是 C++ 标准模板库(STL)中的一个容器,提供在两端高效插入和删除操作。它的内部实现通常使用多个固定大小的数组(块),允许灵活地在两端扩展。
1. 基本概念
std::deque 是一个模板类,定义在 <deque> 头文件中。与 std::vector 类似,std::deque 也支持动态大小,但与 vector 不同的是,deque 可以在头部和尾部都进行高效的插入和删除操作。
#include <deque>
std::deque<int> dq;
2. 主要特性
- 双端插入和删除:支持在头部和尾部进行快速的插入和删除,时间复杂度为 O(1)。
- 动态扩展:
std::deque会根据需要动态调整大小,通常分配多个块来存储元素,以避免移动大量元素。 - 随机访问:虽然访问效率不如
vector,但仍然支持通过下标访问元素,时间复杂度为 O(1)。 - 不连续存储:内部使用分散的内存块,可能不保证所有元素在内存中的连续存储,这使得随机访问的局部性略差于
vector。
3. 常用操作和方法
3.1 初始化
std::deque 可以通过多种方式初始化,如以下几种方式:
// 默认构造函数
std::deque<int> dq1;
// 指定大小的构造函数,所有元素初始化为默认值
std::deque<int> dq2(10);
// 指定大小和初始值
std::deque<int> dq3(10, 5);
// 使用列表初始化
std::deque<int> dq4 = {1, 2, 3, 4, 5};
// 使用拷贝构造函数
std::deque<int> dq5(dq4);
// 使用迭代器范围初始化
std::deque<int> dq6(dq4.begin(), dq4.end());
3.2 添加和删除元素
push_back(value):在尾部添加一个元素。push_front(value):在头部添加一个元素。pop_back():删除尾部的元素。pop_front():删除头部的元素。insert(position, value):在指定位置插入一个元素。erase(position):删除指定位置的元素。clear():清空所有元素。
示例:
std::deque<int> dq = {1, 2, 3};
dq.push_back(4); // dq: {1, 2, 3, 4}
dq.push_front(0); // dq: {0, 1, 2, 3, 4}
dq.pop_back(); // dq: {0, 1, 2, 3}
dq.pop_front(); // dq: {1, 2, 3}
dq.insert(dq.begin(), -1); // dq: {-1, 1, 2, 3}
dq.erase(dq.begin()); // dq: {1, 2, 3}
dq.clear(); // dq: 空
3.3 访问元素
operator[]:直接通过下标访问元素,不进行边界检查。at(index):通过下标访问元素,进行边界检查。front():返回头部元素。back():返回尾部元素。data():返回指向数组首元素的指针。
std::deque<int> dq = {1, 2, 3};
int first = dq.front(); // 1
int last = dq.back(); // 3
int* ptr = dq.data(); // 指向 dq 的首元素的指针
3.4 大小和容量
size():返回当前元素个数。empty():检查是否为空。resize(new_size):调整容器大小,若增大,新元素用默认构造函数初始化。swap(deque):交换两个deque的内容。
std::deque<int> dq(5); // dq 的 size 是 5
bool is_empty = dq.empty(); // false
dq.resize(3); // dq 的 size 变为 3
总结
std::deque 是一个非常灵活的容器,适合需要频繁在头部和尾部插入和删除元素的场景。尽管它支持随机访问,但由于内部存储的分散性,访问效率可能稍逊于 std::vector。
本文概述了C++中的重要数据结构和容器,如vector、队列(queue,priority_queue,stack,deque)、集合(set,map,multiset,multimap)以及bitset,介绍了它们的基本操作和特性,如元素个数、空检查、插入/删除等。
11万+

被折叠的 条评论
为什么被折叠?



