前置背景
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) | 
| 内存开销 | 较高 | 较低,取决于装载因子 | 
| 适用场景 | 需要排序、范围查询或小数据量 | 快速查找、高效插入和大数据量 |