前置背景
C++ STL(Standard Template Library)
是 C++ 标准库的重要组成部分,提供了丰富的数据结构和算法的实现。 STL
是基于泛型编程的,设计目标是高效、灵活和易用。它包含了通用的容器、算法、迭代器和函数对象。
STL 容器用于存储和管理数据,有多种类型,适合不同的应用场景,可分为:序列容器、关联式容器、无序容器。容器都具有的一个基本特性:它保存元素采用的是“值”(value)语义,也就是说, 容器里存储的是元素的拷贝、副本,而不是引用 。所以元素很大,很多的时候,拷贝开销就会很大。一个解决方法式: 尽量为元素实现转移构造和转移赋值函数,在加入容器的时候使用 std::move() 来“转移”,减少元素复制的成本 ,也可以采用 emplace_back
函数来原地构造:
1
2
3
4
5
Point p;
v.push_back(p); // 拷贝成本很高
v.push_back(std::move(p)); // 定义转移构造后可以转移存储
v.emplace_back(...) // 直接在容器内构造
序列容器
按照插入顺序存储元素, 一共有5种: array
, vector
, deque
, list
, forward_list
。其中,连续存储的数组为: array
、 vector
、 deque
;指针结构的链表: list
、 forward_list
。
其中, array
和 vector
为C内置的数组,开销最低,速度最快;array为静态数组,初始化的时候已经固定大小,而 vecor
为动态数组,可以后续随需增长。 deque
也为动态增长的数组,区别为两端高效插入/删除元素。
效率层面, vector
和 deque
的元素为连续存储,支持随机访问,所以插入的效率很低,查找效率高;而list/forward_list式链表结构,插入/删除操作只需要调整指针,更加高效,缺点是查找效率低,另外链表的存储成本也略高,因为每个元素都需要附加指针,指向链表的前后节点。
扩容机制上, vector
当容量达到上限时,会分配两倍大小新内存,然后进行旧元素的拷贝或者移动操作。建议先reserve提前分配空间,减少动态扩容的代价。而 deque
、 list
的扩容策略则更加保守,只按照固定的步长去增加容量。简单的遍历例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {3, 1, 4};
vec.push_back(2); // 在末尾插入
// 按顺序遍历
for (int x : vec) {
std::cout << x << " "; // 输出: 3 1 4 2
}
return 0;
}
关联容器
关联容器,指的是通过 键值对
来存储数据,按照是否有序可以分为:有序容器和无序容器。
有序容器按照插入顺序存储元素,基于键值对存储元素,自动排序。C++有序容器使用的树结构,一般是有着最好查找性能的二叉树(红黑树)。
标准库中一共有四种有序容器: set/multiset
和 map/multimap
。其中 set
是集合, map
是关联数组, multi
支持重复元素/键。特点:
元素按照一定顺序(通常是升序)存储。
插入、删除、查找的时间复杂度为
O(log n)
。支持顺序遍历(如按键值排序的 std::map 或按元素排序的 std::set)。
无序容器基于哈希表实现,元素无序存储。无序容器同样有四种: unordered_set/unordered_multiset
和 unordered_map/unordered_multimap
。其数据结构不是红黑树,而是散列表。
无序容器虽然不要求顺序,但是对 key 的要求反而比有序容器更“苛刻”一些:要求 key 具备两个条件,一是可以计算 hash 值,二是能够执行相等比较操作。
无序容器和有序容器相比:
元素以散列方式存储,无特定顺序。
插入/删除/查找成本低,都为
O(1)
,最坏情况为O(n)
(例如哈希冲突严重时)。无法按照顺序遍历元素。
有序容器由于平衡二叉搜索树需要额外存储父子节点的指针,内存开销较高;而无序容器,哈希表需要额外存储哈希值和处理冲突的链表或桶,内存开销取决于哈希表的装载因子。
简单使用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <string>
#include <map>
int main() {
std::map<int, std::string> m = { {2, 'B'} };
// std::unordered_map<int, std::string> m = { {2, 'B'} };
// 按键顺序遍历
for (auto [key, value] : m) {
std::cout << key << ": " << value << std::endl;
}
return 0;
}
对比如下:
特性 | 有序容器 | 无序容器 |
底层实现 | 平衡二叉搜索树(如红黑树) | 哈希表 |
数据存储顺序 | 按键或值排序存储 | 无序存储 |
查找效率 | O(log n) | 平均 O(1),最坏 O(n) |
插入删除效率 | O(log n) | 平均 O(1),最坏 O(n) |
遍历效率 | 支持顺序遍历,效率为 O(n) | 遍历无序,效率为 O(n) |
内存开销 | 较高 | 较低,取决于装载因子 |
适用场景 | 需要排序、范围查询或小数据量 | 快速查找、高效插入和大数据量 |