STL

STL(Standard Template Library)是C++中的标准模板库组件,由容器、迭代器、算法、适配器、函数对象和空间配置器组成。

  1. 容器用于存储和管理数据对象,也就是数据结构。容器分为序列式容器、有序关联式容器和无序关联式容器。序列式容器中元素的顺序由插入的位置决定,主要有vector, deque和list等。有序关联式容器会对插入的元素进行排序,默认为升序,主要有set,map等。无序关联式容器基于哈希表,提供快速查找的功能,但是元素为乱序,主要有unordered_set和unordered_map。

  2. 迭代器是连接容器和算法的桥梁,是为了便于访问容器中的元素而设计出来的一种统一的访问接口,可以将其看成是一种指针,称为泛型指针。

  3. 算法提供一些通用的函数,用于操作容器中的元素,比如查找元素、排序元素和进行数值运算。

  4. 适配器用于修改其他组件的接口,起到适配的作用,比如容器适配器、迭代器适配器和函数适配器。

  5. 函数对象是重载了operator()的类,可以像函数一样调用,用于做定制化的操作。

  6. 空间配置器负责内存的分配和释放,为容器提供内存管理。

vector

std::vector 是C++ STL中最常用的序列容器,提供动态数组功能。它是一个模板类,可以存储任意类型的元素。

基本特性

声明和初始化

基本操作

迭代器

算法

内部实现原理

内存布局

vector

动态扩容机制

扩容策略

操作时间复杂度说明
随机访问O(1)通过下标或迭代器
尾部插入/删除O(1) 摊销push_back/pop_back
任意位置插入/删除O(n)需要移动元素
查找O(n)线性查找
排序O(n log n)使用std::sort

应用

总结

选择vector的场景:

避免使用vector的场景:

deque

std::deque(double-ended queue,双端队列)是C++ STL中的一个序列容器,支持在两端进行高效的插入和删除操作。

基本特性

声明和初始化

基本操作

双端插入和删除

任意位置插入和删除

调整大小和赋值

迭代器

内部实现原理


deque是由多个片段组成的,片段内部是连续的,但是片段之间不连续的,分散的,多个片段被一个称为中控器的结构控制(也称为map,但跟std::map不是一回事)。

所以说deque是在物理上是不连续的,但是逻辑上是连续的

初始化deque时,根据元素的个数分配一些缓冲区片段

在缓冲区片段中存放实际的元素,这里以int型元素为例。通常情况下,在初始化时第一个元素会被放置在第一个缓冲区中相对靠前的位置(而非第一个缓冲区的首地址,这样方便在头部添加元素)。

前后相邻的两个元素在逻辑上是连续的,但是物理层面上可能并不是连续的。

同时还会生成一个数组中控器(map),存放每个缓冲区片段的首地址。(注意map中的元素是int*,而不是严格意义的数组指针,这一点从源码中可以进行理解)

通常情况下,第一个缓冲区片段的首地址被放在中控器map的居中位置。


deque初始化完成后,进行添加元素的操作:

如果在尾部添加元素value

找到最后一个存放着元素的缓冲区片段,在其中最后一个元素的后面添加元素value。

如果此时最后的缓冲区片段是满的,那么就再申请一个新的缓冲区片段,将要添加的元素value存放在新片段的首个位置。同时中控器也加上一个元素(新片段的首地址),让中控器与新片段联系起来。

如果此时中控器已经满了,还要添加内容,那么开辟新的更大的空间作为中控器,将原本中控器中记录的地址值复制到新的中控器,并将最后一个缓冲区片段的首地址加入。


如果是在deque的头部添加元素value

找到已经存储的第一个元素的位置,在它的前面存放新元素value。

经过若干次头部添加元素之后,如果第一个缓冲区片段满了,还要继续在头部添加元素,则新开辟一个缓冲区片段(作为第一个片段),将value存在这个片段中的最后一个位置。同时在中控器的相应位置存放新的缓冲片段的首地址。

如果一直往deque头部添加元素,中控器的前半部分已经满了,后半部分还没满,就将中控器中记录的地址全都后移,将这个第一个缓冲区的首地址存入中控器的第一个位置。如果中控器全都满了,那就再开辟新的更大的空间作为中控器。

deque

Tip

每个缓冲区片段的大小是多大?

如果deque存放的元素类型的大小小于 512 字节。每个缓冲区片段的大小为 512 字节,512 除以元素类型的大小,就可以得到每个缓冲区片段能够存放的元素数量。 例如元素类型是 int,在 64 位系统中,sizeof(int)= 4 字节。那么 deque_buf_size(4) 的结果就是 512 / 4 = 128,即每个缓冲区片段可以存放 128 个 int 类型的元素。 这种设计的好处是,对于较小的元素类型,每个缓冲区可以存放较多的元素,减少了缓冲区的数量,从而降低了中控器(用于管理缓冲区的数组)的管理开销。

如果元素类型大小大于等于 512 字节,函数返回 1。这表示每个缓冲区片段只存放一个元素。 比如自定义类型A的对象作为deque的元素,单个元素的大小超过了 512 字节,那么 __deque_buf_size(sizeof(A)) 的结果就是 1,每个缓冲区片段只会存放一个 A类型的元素。

Note

deque的迭代器底层是和vector一样吗?

deque的迭代器比vector的迭代器复杂得多,vector<T>的迭代器实际是对T*的包装,但deque的迭代器实际应该视为对象,数据成员包含四个指针,并且进行了一系列的运算符重载,使得迭代器能够像指针一样被使用。

deque_start

deque_finish

操作时间复杂度说明
随机访问O(1)通过下标或迭代器
头部插入/删除O(1)push_front/pop_front
尾部插入/删除O(1)push_back/pop_back
任意位置插入/删除O(n)需要移动元素
查找O(n)线性查找
需求推荐容器原因
频繁尾部操作vector最佳性能,连续内存
频繁双端操作deque双端O(1)操作
频繁中间插入删除list任意位置O(1)操作
需要随机访问vector/deque支持O(1)随机访问
内存使用敏感vector最少的内存开销
迭代器稳定性list插入删除不影响其他迭代器

算法

应用

滑动窗口

双端队列应用

缓存系统

总结

std::deque 是一个功能强大的容器,具有以下优势:

适用场景

注意事项

选择deque的关键是:当需要双端操作的高效性,同时又需要随机访问能力时,deque是最佳选择。

list

std::list 是 C++ STL 中的双向链表容器,提供了高效的插入和删除操作。

基本特性

声明和初始化

基本操作

迭代器

list特有操作

1. 拼接操作

2. 排序和去重

3. 合并操作

4. 反转

算法

内部实现原理

操作时间复杂度说明
插入/删除(任意位置)O(1)已知迭代器位置
查找O(n)需要线性搜索
访问首尾元素O(1)直接访问
排序O(n log n)归并排序
拼接O(1)指针操作
大小获取O(1)维护计数器

应用

LRU缓存实现

音乐播放列表

撤销/重做功能

总结

优势

  1. 高效插入删除:任意位置O(1)时间复杂度

  2. 稳定迭代器:插入删除不影响其他迭代器

  3. 灵活拼接:高效的链表合并操作

  4. 内存效率:按需分配,无预分配浪费

劣势

  1. 无随机访问:不能通过下标直接访问元素

  2. 内存开销大:每个元素需要额外的指针空间

  3. 缓存不友好:非连续内存布局影响性能

  4. 遍历较慢:相比连续容器,遍历性能较差

适用场景

std::list 是实现双向链表的标准容器,在需要频繁插入删除且不需要随机访问的场景下是最佳选择。

set

std::set 是 C++ STL 中的关联容器,基于红黑树实现,存储唯一的有序元素。

基本特性

核心特征

底层实现

声明和初始化

基本操作

插入操作

查找操作

删除操作

大小和状态

迭代器

自定义比较器

1. 函数对象比较器

2. Lambda表达式比较器

3. 函数指针比较器

集合运算

1. 使用STL算法进行集合运算

2. 自定义集合运算函数

算法

内部实现原理

1. 红黑树结构

2. 红黑树性质

  1. 节点颜色:每个节点要么是红色,要么是黑色

  2. 根节点:根节点是黑色

  3. 叶节点:所有叶节点(NIL)都是黑色

  4. 红色节点:红色节点的子节点必须是黑色

  5. 黑色高度:从任一节点到其叶节点的所有路径包含相同数量的黑色节点

操作平均时间复杂度最坏时间复杂度
查找O(log n)O(log n)
插入O(log n)O(log n)
删除O(log n)O(log n)
遍历O(n)O(n)

应用

去重和排序

词频统计

权限管理系统

图的邻接表表示

区间集合

总结

优势

  1. 自动排序:元素始终保持有序状态

  2. 唯一性保证:自动去重,避免重复元素

  3. 对数时间复杂度:查找、插入、删除都是O(log n)

  4. 稳定的迭代器:插入删除不会使其他迭代器失效

  5. 丰富的查找功能:支持范围查找、边界查找

  6. 集合运算支持:配合STL算法进行集合运算

劣势

  1. 内存开销大:每个节点需要额外的指针和颜色信息

  2. 无随机访问:不能通过下标直接访问元素

  3. 插入性能:比哈希表慢,比vector的尾部插入慢

  4. 缓存不友好:非连续内存布局影响性能

  5. 元素不可修改:插入后不能直接修改元素值

适用场景

std::set 是实现有序唯一集合的理想选择,在需要自动排序和去重的场景下表现优异。

map

std::map 是 C++ STL 中的关联容器,基于红黑树实现,存储键值对并按键自动排序。

基本特性

1. 核心特征

2. 底层实现

声明和初始化

基本操作

1. 插入操作

2. 访问操作

3. 删除操作

4. 大小和状态

迭代器

自定义比较器

1. 函数对象比较器

2. Lambda表达式比较器

3. 函数指针比较器

高级用法

嵌套map

map作为计数器

map的值为复杂类型

算法

内部实现原理

1. 红黑树节点结构

2. 时间复杂度分析

操作平均时间复杂度最坏时间复杂度
查找O(log n)O(log n)
插入O(log n)O(log n)
删除O(log n)O(log n)
遍历O(n)O(n)
下标访问O(log n)O(log n)

3. 内存布局

应用

配置管理系统

学生成绩管理系统

缓存系统

状态机实现

惰性求值映射

总结

优势

  1. 自动排序:键值对按键自动排序

  2. 唯一键约束:确保每个键只出现一次

  3. 对数时间复杂度:查找、插入、删除都是O(log n)

  4. 稳定的迭代器:插入删除不会使其他迭代器失效

  5. 丰富的查找功能:支持范围查找、边界查找

  6. 直观的接口:下标操作符提供直观的访问方式

劣势

  1. 内存开销大:每个节点需要额外的指针和颜色信息

  2. 无随机访问:不能通过索引直接访问元素

  3. 插入性能:比哈希表慢

  4. 缓存不友好:非连续内存布局影响性能

  5. 下标操作副作用:可能意外插入新元素

适用场景

std::map 是实现有序键值映射的理想选择,在需要自动排序和唯一键约束的场景下表现优异。

unordered_set

std::unordered_set 是 C++11 引入的基于哈希表实现的关联容器,用于存储唯一元素的无序集合。

基本特性

核心特征

底层实现

声明和初始化

自定义哈希和相等比较

基本操作

插入操作

查找操作

删除操作

迭代器和遍历

容量和桶操作

大小和容量

桶操作

哈希策略控制

算法

内部实现原理

应用

集合操作实现

去重和存在性检查

缓存系统

图算法应用

总结

优势

  1. 高效查找:平均 O(1) 时间复杂度

  2. 动态大小:自动扩容和收缩

  3. STL兼容:与标准算法良好集成

  4. 类型安全:模板提供编译时类型检查

劣势

  1. 无序性:不保持元素顺序

  2. 内存开销:哈希表需要额外空间

  3. 最坏情况:所有元素冲突时性能退化为 O(n)

  4. 哈希依赖:性能很大程度上依赖哈希函数质量

适用场景

std::unordered_set 在需要高效集合操作的场景中是首选。

unordered_map

std::unordered_map 是 C++11 引入的基于哈希表实现的关联容器,用于存储键值对(key-value pairs),其中键是唯一的。

基本特性

核心特征

底层实现

声明和初始化

自定义哈希和相等比较

基本操作

插入操作

访问操作

删除操作

迭代器和遍历

容量和桶操作

大小和容量

桶操作

哈希策略控制

算法

内部实现原理

应用

缓存系统

词频统计

配置管理系统

图的邻接表表示

容器的选择

容器概览

序列容器(Sequence Containers)

关联容器(Associative Containers)

无序关联容器(Unordered Associative Containers)

容器适配器(Container Adapters)

选择指南

序列容器选择

vector - 首选的序列容器

选择vector的场景:

deque - 双端操作优化

选择deque的场景:

list - 任意位置高效插入

选择list的场景:

关联容器选择

map vs unordered_map

选择map的场景:

选择unordered_map的场景:

容器适配器选择

性能特性对比表

容器随机访问插入(头)插入(尾)插入(中)删除(头)删除(尾)删除(中)查找
vectorO(1)O(n)O(1)*O(n)O(n)O(1)O(n)O(n)
dequeO(1)O(1)O(1)O(n)O(1)O(1)O(n)O(n)
listO(n)O(1)O(1)O(1)O(1)O(1)O(1)O(n)
set---O(log n)--O(log n)O(log n)
unordered_set---O(1)*--O(1)*O(1)*
map---O(log n)--O(log n)O(log n)
unordered_map---O(1)*--O(1)*O(1)*

*摊销时间复杂度


主要操作推荐容器原因
随机访问vector, deque, arrayO(1)访问时间
末尾插入vector摊销O(1),内存连续
头部插入deque, listO(1)插入时间
中间插入listO(1)插入,不需移动元素
查找元素set, unordered_setO(log n)或O(1)查找
有序遍历set, map自动维护顺序
键值查找map, unordered_map高效的键值对操作