C++ 标准库中的容器类是日常开发的核心工具,它们各自有不同的底层实现和适用场景。下面我会按序列式容器容器适配器关联式容器三大类来梳理这些容器的核心特点:

一、序列式容器(关注元素的存储顺序)

序列式容器的元素按插入顺序排列,访问方式主要依赖位置(索引或迭代器)。

1. vector(动态数组)
  • 底层实现:连续内存空间的动态数组(内存不够时会重新分配更大的空间并复制元素)。
  • 核心特点
    • 支持随机访问(通过[]at(),时间复杂度 O (1))。
    • 尾部插入 / 删除元素高效(O (1)),中间 / 头部插入 / 删除低效(需要移动大量元素,O (n))。
    • 内存连续,缓存友好,适合频繁访问元素的场景。
  • 适用场景:需要频繁随机访问、尾部操作多的场景(如存储列表数据、动态数组)。
2. deque(双端队列)
  • 底层实现:分段连续的内存块(由一个 "中控器" 管理多个固定大小的数组)。
  • 核心特点
    • 支持双端高效操作(头部和尾部插入 / 删除都是 O (1))。
    • 随机访问效率比vector略低(需要先找对应内存块,再访问元素,O (1) 但常数更大)。
    • 内存不连续但分段连续,避免vector扩容时的大量复制。
  • 适用场景:需要频繁在两端操作的场景(如实现队列、滑动窗口)。
3. list(双向链表)
  • 底层实现:双向链表(每个节点包含数据、前驱指针、后继指针)。
  • 核心特点
    • 不支持随机访问(访问元素需从头部 / 尾部遍历,O (n))。
    • 任意位置插入 / 删除高效(只需修改指针,O (1),前提是已找到位置)。
    • 内存不连续,缓存不友好,空间开销比数组大(需存储指针)。
  • 适用场景:频繁在中间位置插入 / 删除元素的场景(如链表操作、频繁修改的列表)。
4. forward_list(单向链表)
  • 底层实现:单向链表(每个节点只包含数据和后继指针)。
  • 核心特点
    • list更节省空间(少一个前驱指针),但只能单向遍历。
    • 同样不支持随机访问,任意位置插入 / 删除高效(O (1),需找到前驱节点)。
  • 适用场景:内存受限,且只需单向遍历、频繁修改的场景(功能类似list但更轻量)。

二、容器适配器(基于其他容器封装的特殊功能容器)

适配器不直接存储数据,而是通过封装底层容器(如vector/deque/list)提供特定接口。

1. stack(栈)
  • 底层依赖:默认基于deque实现,也可指定vectorlist(通过模板参数stack<T, Container>)。
  • 核心特点
    • 遵循LIFO(后进先出) 原则,只能访问栈顶元素。
    • 提供push()(入栈)、pop()(出栈)、top()(访问栈顶)操作,无迭代器。
  • 适用场景:需要栈结构的场景(如表达式求值、递归模拟、括号匹配)。
2. queue(队列)
  • 底层依赖:默认基于deque实现,也可指定list(不能用vector,因vector头部删除低效)。
  • 核心特点
    • 遵循FIFO(先进先出) 原则,只能访问队头和队尾元素。
    • 提供push()(入队)、pop()(出队)、front()(队头)、back()(队尾)操作,无迭代器。
  • 适用场景:需要队列结构的场景(如广度优先搜索、任务调度)。

三、关联式容器(关注元素的查找和唯一性)

关联式容器的元素按 "键"(key)组织,核心功能是高效查找(区别于序列式容器的 "按位置访问")。

1. set(有序集合)
  • 底层实现:通常是红黑树(一种平衡二叉搜索树)。
  • 核心特点
    • 存储唯一的元素(键即值,不允许重复),且元素自动按升序排序(可自定义比较器)。
    • 插入、删除、查找效率为 O (log n)(红黑树的平衡特性保证)。
    • 支持范围查找(如lower_bound()/upper_bound())。
  • 适用场景:需要去重且元素有序的场景(如存储唯一 ID 并按顺序遍历)。
2. unordered_set(无序集合)
  • 底层实现哈希表(通过哈希函数映射到桶,解决哈希冲突)。
  • 核心特点
    • 存储唯一的元素,但无序(不支持排序相关操作)。
    • 插入、删除、查找平均效率为 O (1)(哈希函数均匀时),最坏 O (n)(哈希冲突严重)。
    • 不支持范围查找,空间开销比set大(需存储哈希表结构)。
  • 适用场景:需要快速去重和查找,且不关心元素顺序的场景(如判断元素是否存在)。
3. map(有序映射)
  • 底层实现:通常是红黑树,键值对(key-value)结构,键唯一。
  • 核心特点
    • 存储键值对,键唯一且自动按升序排序(可自定义比较器)。
    • 通过键查找值的效率为 O (log n),支持根据键的范围遍历。
    • 可通过[]at()直接访问键对应的值(若键不存在,[]会插入默认值)。
  • 适用场景:需要键值对映射且键有序的场景(如字典、配置表)。
4. unordered_map(无序映射)
  • 底层实现哈希表,键值对结构,键唯一。
  • 核心特点
    • 存储键值对,键唯一但无序
    • 通过键查找值的平均效率为 O (1),最坏 O (n)。
    • 同样支持[]访问,空间开销比map大。
  • 适用场景:需要快速键值查找且不关心键顺序的场景(如缓存、计数统计)。

总结:选择容器的核心依据

  1. 是否需要随机访问?→ 选vector/deque(序列式)。
  2. 是否需要频繁插入删除?→ 中间操作选list,两端操作选deque
  3. 是否需要栈 / 队列特性?→ 直接用stack/queue适配器。
  4. 是否需要去重或键值映射?→ 有序选set/map,追求速度无序选unordered_*

理解它们的底层实现(数组 / 链表 / 红黑树 / 哈希表),就能快速记住各自的优缺点和适用场景了。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐