STL 容器

C++ STL(Standard Template Library)中主要提供了三大类通用容器,用于存储和管理不同类型和结构的数据。它们分别是:

STL 容器总览

容器类别 容器 说明
序列式容器 (Sequence Containers) vector, deque, list, forward_list, array 元素按插入顺序排列,类似数组
关联式容器 (Associative Containers) set, map, multiset, multimap 自动排序,基于平衡树(红黑树)
无序关联容器 (Unordered Associative Containers) unordered_set, unordered_map, unordered_multiset, unordered_multimap 无序哈希表,查找插入效率高
容器适配器 (Container Adapters) stack, queue, priority_queue 封装其他容器,提供特定行为

序列式容器(有顺序,按位置存储)

容器名 特点 常见用途
vector 动态数组,支持随机访问,尾部插入快 默认首选容器,替代 C 数组
deque 双端队列,支持首尾插入删除 双端栈、滚动窗口
list 双向链表,插入删除快,不能随机访问 频繁插入/删除位置稳定
forward_list 单向链表,内存更小(C++11) 内嵌链表、只需前向遍历
array 定长数组,封装 C 风格数组(C++11) 栈上存储,性能好,长度固定

一、序列式容器特性对比速查表

特性维度 vector array deque list forward_list
底层结构 动态数组(连续内存) 静态数组(连续内存) 分段数组 双向链表 单向链表
大小可变 ✅ 是 ❌ 否 ✅ 是 ✅ 是 ✅ 是(无 size()
下标访问 ([], at()) ✅ 支持 ✅ 支持 ✅ 支持 ❌ 不支持 ❌ 不支持
头部操作 (push_front) ❌ 慢(需移动) ✅ 高效 ✅ 高效 ✅ 高效
尾部操作 (push_back) ✅ 高效 ✅ 高效 ✅ 高效 ❌ 无此接口
中间插入/删除 ❌ 慢(移动元素) ⚠️ 中等偏慢 ✅ 高效 ✅ 但不方便
空间管理函数 reserve()shrink_to_fit()
支持 data() ✅(C++17 起)
支持 size() ❌(需手动计数)
迭代器类型 随机访问 随机访问 随机访问 双向 单向(forward only)
STL算法兼容性 ✅ 全兼容 ✅ 全兼容 ✅ 全兼容 ⚠️ 不能用 std::sort() 同左
稳定性(指针/引用稳定) ❌ 增删会失效 ✅ 稳定 ✅ 稳定
排序支持 std::sort() std::sort() std::sort() .sort()(成员函数) .sort()
内存分配位置 堆(分段) 堆(节点) 堆(节点)
适用场景 随机访问、连续优化、频繁尾部插入 定长数组替代品、轻量 双端队列、滑动窗口 频繁中间修改 单向处理流、大量前插、轻量链表

deque 内存分块不支持空间管理函数,vector 是一片连续的内存,支持 data()reserve()shrink_to_fit() 等内存操作;

listforward_list内部结构是链表,没有下标操作,链表自带排序api;

array 在编译时就确定了大小,不能动态改变元素的数量,内存分配在栈上;

  • vector:适合连续内存优化,支持 data()reserve()shrink_to_fit() 等内存操作,尾部插入效率高,但头部和中间插入较慢。
  • array:固定大小的轻量容器,栈上分配,支持下标访问和 data(),不支持任何动态操作。
  • deque:支持头部和尾部插入操作,适合双端队列场景,但不支持空间管理函数,底层为分段内存结构。
  • list:双向链表容器,适合频繁的中间插入和删除,元素地址稳定,但不支持下标访问,也不能用 std::sort()(需要用成员 sort())。
  • forward_list:单向链表容器,接口更轻量,不支持尾部插入和 size(),只能使用 insert_after() / erase_after() 等后置操作,适合嵌入式或轻量场景。

二、共性 API 总结

几乎所有序列式容器都支持如下操作(但有些容器支持受限):

类别 API 说明
容器大小 size() 返回当前元素个数
empty() 是否为空
max_size() 可容纳最大元素数
元素访问 front() / back() 返回头/尾元素(不能用于 forward_listback()
迭代器 begin() / end() 常规迭代器
cbegin() / cend() const 版本
rbegin() / rend() 反向迭代器
修改操作 insert() 插入元素(forward_list 仅支持 after)
erase() 删除元素(forward_list 仅支持 after)
push_back() / pop_back() 尾部插入/删除(forward_list 无)
push_front() / pop_front() 头部插入/删除(vector 无)
clear() 清空容器
resize() 改变大小
其他 swap() 交换两个容器
assign() 批量赋值(如指定区间或重复值)

二、每个容器详细 API + 特性对比


1. vector<T>

cpp 复制代码
#include <vector>
  • std::vector 是一个动态数组容器
  • 元素连续存储,支持随机访问
  • 尾部插入/删除效率高,插入中间位置效率较低

std::vector 的常用构造函数构造函数

构造函数 说明
vector() 默认构造,空容器
vector(size_t n) 创建含 n 个默认值元素的容器
vector(size_t n, const T& val) 创建含 n 个值为 val 的元素
vector(initializer_list<T>) 使用花括号初始化列表构造(C++11)
vector(iter1, iter2) 通过迭代器区间构造
vector(const vector& other) 拷贝构造
vector(vector&& other) 移动构造(C++11)

具体例子:

构造方式 示例 说明
默认构造 vector<int> v; 创建空容器
指定大小 vector<int> v(5); 创建 5 个默认元素(0)
指定大小和值 vector<int> v(3, 9); 创建 3 个值为 9 的元素
初始化列表 vector<int> v = {1, 2}; 推荐用法,清晰直观
区间构造 vector<int> v(v2.begin(), v2.end()); 从其他容器复制子区间
拷贝构造 vector<int> v(v2); 拷贝另一个 vector
移动构造 vector<int> v(std::move(v2)); 高效转移所有权(C++11)

示例代码:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    // 1. 默认构造
    std::vector<int> v1;
    std::cout << "v1.size = " << v1.size() << "\n";

    // 2. 构造 n 个默认值元素
    std::vector<int> v2(5);
    std::cout << "v2: ";
    for (int x : v2) std::cout << x << " ";  // 输出 0 0 0 0 0
    std::cout << "\n";

    // 3. 构造 n 个指定值
    std::vector<int> v3(4, 7);  // 4 个 7
    std::cout << "v3: ";
    for (int x : v3) std::cout << x << " ";  // 输出 7 7 7 7
    std::cout << "\n";

    // 4. 使用初始化列表
    std::vector<int> v4 = {1, 2, 3, 4};
    std::cout << "v4: ";
    for (int x : v4) std::cout << x << " ";  // 输出 1 2 3 4
    std::cout << "\n";

    // 5. 通过迭代器范围构造(从 v4 拷贝前两个元素)
    std::vector<int> v5(v4.begin(), v4.begin() + 2);
    std::cout << "v5: ";
    for (int x : v5) std::cout << x << " ";  // 输出 1 2
    std::cout << "\n";

    // 6. 拷贝构造
    std::vector<int> v6 = v3;
    std::cout << "v6: ";
    for (int x : v6) std::cout << x << " ";  // 输出 7 7 7 7
    std::cout << "\n";

    // 7. 移动构造(C++11)
    std::vector<int> v7 = std::move(v6);
    std::cout << "v7: ";
    for (int x : v7) std::cout << x << " ";  // 输出 7 7 7 7
    std::cout << "\n";
}

增(插入)操作

  1. push_back(val) --- 在尾部插入一个元素
cpp 复制代码
std::vector<int> v;
v.push_back(10);
v.push_back(20);  // v = {10, 20}
  1. insert(pos, val) --- 在指定位置插入一个元素
cpp 复制代码
auto it = v.begin();  // 指向第一个元素
v.insert(it + 1, 15); // 在索引1处插入 15:v = {10, 15, 20}
  1. insert(pos, count, val) --- 插入多个相同元素
cpp 复制代码
v.insert(v.begin(), 3, 7); // v = {7, 7, 7, 10, 15, 20}
  1. insert(pos, range_begin, range_end) --- 插入迭代器范围
cpp 复制代码
std::vector<int> other = {1, 2};
v.insert(v.end(), other.begin(), other.end());  // 尾部插入 1, 2

删(删除)操作

  1. pop_back() --- 删除最后一个元素
cpp 复制代码
v.pop_back();  // 删除末尾元素 2
  1. erase(pos) --- 删除指定位置的元素
cpp 复制代码
v.erase(v.begin() + 1);  // 删除索引1处元素
  1. erase(begin, end) --- 删除范围内元素
cpp 复制代码
v.erase(v.begin(), v.begin() + 2);  // 删除前两个元素
  1. clear() --- 清空所有元素
cpp 复制代码
v.clear();  // v.size() == 0

改(修改)操作

  1. 使用 operator[] 修改元素
cpp 复制代码
std::vector<int> v = {1, 2, 3};
v[1] = 10;  // v = {1, 10, 3}
  1. 使用 at(index)(带越界检查)
cpp 复制代码
v.at(2) = 99;  // v = {1, 10, 99}

⚠️ 如果越界,at() 会抛出 std::out_of_range 异常。


查(访问)操作

  1. 随机访问
cpp 复制代码
int x = v[0];
int y = v.at(1);  // 推荐安全版本
  1. 访问首尾元素
cpp 复制代码
int first = v.front();
int last = v.back();
  1. 遍历(推荐 range-for)
cpp 复制代码
for (int val : v) {
    std::cout << val << " ";
}

也可以用传统迭代器:

cpp 复制代码
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}

其他操作

resize(n) --- 修改容器大小

cpp 复制代码
v.resize(5);  // 增大到5个元素,自动补 0
v.resize(2);  // 缩小为2个元素

reserve(capacity) --- 预分配内存(避免多次扩容)

cpp 复制代码
v.reserve(100);  // 分配100个元素空间,提高效率

shrink_to_fit() --- 回收冗余空间

vector::shrink_to_fit() 是 C++11 引入的一个标准库成员函数,作用是:请求 释放多余的内存容量,使 vector 的容量(capacity)收缩到与当前元素个数(size)相同,减少内存占用。

cpp 复制代码
v.shrink_to_fit();

data() 返回指向 vector 内部元素数组的指针

std::vector::data() 是一个成员函数,其作用是:返回指向 vector 内部元素数组的指针(也就是首元素的指针),你可以通过它访问底层的原始连续内存。

data() 给你一个 T\* 指针 ,指向 vector 内部的连续数组。

cpp 复制代码
T* data();              // 返回可写指针(C++11 起)
const T* data() const;  // 返回只读指针

示例

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3};

    int* ptr = v.data();

    ptr[1] = 42;  // 直接通过指针修改 vector 中的元素

    std::cout << v[1] << "\n";  // 输出 42
}

注意事项

  1. vector 的元素是 连续的内存块 ,所以 data() 指针指向的是合法数组首地址。
  2. 如果 vector 是空的(size() == 0),返回的指针 不是 nullptr,但不能解引用
  3. 若后续对 vector 执行了插入/删除/扩容操作,原 data() 返回的指针可能会失效
表达式 含义 备注
v.data() 返回首元素地址指针 推荐方式,语义清晰
&v[0] 同样返回首地址 C++03 常用,不安全(空 vector 会崩溃)

示例完整程序

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;

    // 增
    v.push_back(1);
    v.push_back(2);
    v.insert(v.begin(), 0);      // 插入到开头
    v.insert(v.end(), 2, 99);    // 插入两个 99

    // 改
    v[1] = 100;        // 修改第 1 个元素
    v.at(2) = 200;

    // 查
    std::cout << "Front: " << v.front() << ", Back: " << v.back() << "\n";

    std::cout << "遍历:";
    for (int x : v) std::cout << x << " ";
    std::cout << "\n";

    // 删
    v.pop_back();       // 删除最后一个 99
    v.erase(v.begin()); // 删除第一个元素
    v.clear();          // 清空

    std::cout << "Size after clear: " << v.size() << "\n";
}

总结表格:vector 增删改查 API

操作 方法 说明
push_back(val) 尾部插入
insert(pos, val) 指定位置插入
pop_back() 删除末尾元素
erase(pos) 删除指定元素
clear() 清空
v[i] = x, v.at(i) 修改第 i 个元素
v[i], v.at(i) 访问第 i 个元素
front(), back() 首尾元素
begin(), end() 迭代器遍历

2. deque<T>

std::dequedouble-ended queue )是由多个连续的小块内存(称为块、block、buffer)*构成的*分段动态数组 ,这些小块由一个中央控制结构(map,通常是指针数组)*统一管理,因此整体上*不连续deque 支持头部操作但不支持空间管理函数,vector 适合连续内存优化,支持 data()reserve()shrink_to_fit() 等内存操作。 vectordequeAPI 层面上虽然大多数接口相同,但也存在以下几点显著差异**:


构造函数

与vector的构造函数很类似

构造函数形式 功能描述 示例
deque() 默认构造,创建空容器 std::deque<int> dq;
deque(size_type n) 创建包含 n 个默认值元素的容器 std::deque<int> dq(5);
deque(size_type n, const T& val) 创建包含 n 个值为 val 的元素 std::deque<int> dq(3, 42);
deque(InputIt first, InputIt last) 用迭代器区间 [first, last) 构造容器 std::deque<int> dq(vec.begin(), vec.end());
deque(const deque& other) 拷贝构造函数,复制另一个容器 std::deque<int> dq2(dq1);
deque(deque&& other) (C++11) 移动构造函数,接管另一个容器资源 std::deque<int> dq2(std::move(dq1));
deque(std::initializer_list<T> ilist) (C++11) 使用初始化列表构造容器 std::deque<int> dq = {1, 2, 3};

与 vector API 上的区别

功能类别 vector deque 说明
头部插入/删除 ❌ 无 push_front / pop_front ✅ 有 push_front / pop_front 这是最明显的区别之一
内存管理 reserve() / capacity() / shrink_to_fit() ❌ 不支持这些函数 deque 不暴露连续空间概念
随机访问 operator[], at() ✅ 同样支持 接口一样,性能略有差别
构造函数与赋值 ✅ 基本一致 ✅ 基本一致 如构造、assign() 等均支持
数据指针获取 data() ⚠️ 无 data() deque 元素非连续,无法获取底层指针
空间大小控制 capacity() 表示分配空间大小 ❌ 无此 API deque 内部是block链表,不存在 capacity

与 vector 内存结构 上的区别

特性 vector deque
内存结构 一整块连续内存 多块分段内存(非连续)
指针访问 data() 可用 data()(不能整体访问)
局部性(cache 命中) 优秀(线性) 较差(跨 block)
增长方式 一次性重新分配更大空间 增加新的 block

deque内部结构图(简化示意)

复制代码
std::deque 内存布局示意:

        map(指针数组)
        ↓
  +-----+-----+-----+-----+
  |  →  |  →  |  →  |  →  |   ← 控制中心
  +-----+-----+-----+-----+
     ↓     ↓     ↓     ↓
   block  block block block
   [x x]  [x x] [x x] [x x] ← 实际元素在这些 block 中存储(固定大小)
  • 每个 block 通常存储 512~4096 字节左右(依元素类型大小决定)
  • 当 deque 两端插入导致空间不足时,它会:
    • 分配新 block(前/后扩展)
    • 可能重新分配指针数组(map)

实验验证deque 内存不连续

你可以验证 deque 是否连续:

cpp 复制代码
#include <iostream>
#include <deque>

int main() {
    std::deque<int> dq;

    // 插入较多元素,促使 deque 使用多个 block
    for (int i = 0; i < 1000; ++i) {
        dq.push_back(i);
    }

    // 打印地址,并检测地址跳变
    const void* last_addr = nullptr;

    for (size_t i = 0; i < dq.size(); ++i) {
        const void* addr = static_cast<const void*>(&dq[i]);

        if (last_addr != nullptr) {
            // 判断是否连续(以 sizeof(int) 为步长)
            std::ptrdiff_t diff = static_cast<const char*>(addr) - static_cast<const char*>(last_addr);
            if (diff != sizeof(int)) {
                std::cout << "⚠️  Address jump detected at i = " << i
                          << " (diff = " << diff << " bytes)\n";
            }
        }

        // 输出地址
        std::cout << "dq[" << i << "] = " << dq[i]
                  << " | address: " << addr << "\n";

        last_addr = addr;
    }

    return 0;
}

常用函数一览表

类别 函数 说明
push_back(val) 尾部插入
push_front(val) 头部插入
insert(pos, val) 插入元素
pop_back() / pop_front() 删除尾/头元素
erase(pos) / clear() 删除指定位置 / 全部
dq[i] = x / dq.at(i) 修改第 i 个元素
dq[i] / at(i) / front() / back() 访问元素
其他 size(), empty(), begin(), end() 常规容器操作

3. list<T>

虽然 std::liststd::deque 都支持双端插入和删除 ,但它们在底层结构、功能特性和 API 上存在一些明显区别

deque双端数组容器 ,支持随机访问;
list双向链表容器,支持常量时间的中间插入删除。


构造函数

构造函数形式 功能描述 示例
list() 默认构造,创建空链表容器 std::list<int> lst;
list(size_type n) 创建包含 n 个默认值元素的链表 std::list<int> lst(5);
list(size_type n, const T& val) 创建包含 n 个值为 val 的元素 std::list<int> lst(3, 42);
list(InputIt first, InputIt last) 用迭代器区间 [first, last) 构造链表 std::list<int> lst(vec.begin(), vec.end());
list(const list& other) 拷贝构造,复制另一个链表 std::list<int> lst2(lst1);
list(list&& other) (C++11) 移动构造,接管另一个链表资源 std::list<int> lst2(std::move(lst1));
list(std::initializer_list<T> ilist) (C++11) 初始化列表构造 std::list<int> lst = {1, 2, 3};

API 区别总览(重点)

功能类别 std::deque std::list 区别说明
随机访问 ✅ 支持 operator[], at() ❌ 不支持 list 无法直接访问第 i 个元素
访问接口 .at(i), [i], .front(), .back() .front(), .back() list 没有下标访问
迭代器 双向迭代器 双向迭代器 都是 bidirectional(list 不能做随机跳跃)
插入位置 .insert(pos, val) ✅ 同上 接口类似,list 插入效率更高
删除元素 .erase(pos) .erase(pos), .remove(val) list 多了 remove()unique()
移动元素 ❌ 不支持 splice() list 可直接移动其他 list 的节点(不拷贝)
排序 ❌ 需 std::sort(必须 random access) ✅ 内建 .sort() list 内建排序,不用 std::sort
内存结构 分段数组 双向链表 list 节点分散,不连续
内存局部性 deque 在性能上通常比 list 更好

list 独有 API

cpp 复制代码
std::list<int> lst = {1, 2, 2, 3, 3, 4};

lst.remove(2);      // 删除所有值为2的元素
lst.unique();       // 去重:相邻重复元素只保留一个
lst.sort();         // 内建排序(非随机访问)

4. forward_list<T>(C++11)

std::forward_list单向链表 ,轻量、高效但功能少;
std::list双向链表,功能强大但相对更重。


forward_list vs list API 对比总览

功能类别 std::forward_list std::list 区别说明
链表类型 单向链表 双向链表 方向不同,结构完全不同
迭代器类型 单向(forward_iterator 双向(bidirectional_iterator forward_list 只能从前往后走
遍历方向 只能正向遍历 支持正向和反向遍历 list 支持 reverse_iterator
头部插入 .push_front() / .emplace_front() ✅ 支持
尾部插入 ❌ 无 .push_back() .push_back() / .emplace_back()
中间插入 .insert_after(pos, val) .insert(pos, val) 插入方式不同
中间删除 .erase_after(pos) .erase(pos) 删除方式也不同
删除所有指定值 .remove(val) .remove(val) ✅ 两者都支持
排序 .sort() .sort() ✅ 都内建排序(非 std::sort
合并 .merge() .merge() ✅ 都支持(要求排序)
去重 .unique() .unique() ✅ 都支持
大小查询 ❌ 无 .size() .size() forward_list 需手动统计
反转 .reverse() .reverse() ✅ 都支持
splice(转移节点) .splice_after() .splice() 名字和语义不同

示例对比

cpp 复制代码
std::list<int> lst = {1, 2, 3, 4};
auto it = lst.begin();
++it;
lst.insert(it, 99);  // 在第二个位置插入
lst.erase(it);       // 删除第二个元素

std::forward_list<int> fl = {1, 2, 3, 4};
auto it = fl.before_begin();  // 注意:必须使用 before_begin()
fl.insert_after(it, 99);      // 在开头插入
fl.erase_after(it);           // 删除开头后一个元素

本质结构差异图示:

复制代码
std::forward_list (单向):
[1] → [2] → [3] → nullptr

std::list (双向):
nullptr ← [1] ⇄ [2] ⇄ [3] → nullptr

为什么没有push_back api

性能问题push_back 操作意味着在链表的末尾添加一个新元素。然而,在 std::forward_list 中,要找到链表的末尾需要从头开始遍历整个链表,因为每个节点只知道它的下一个节点是谁,而不知道它在整个链表中的位置或者如何快速访问最后一个节点。这意味着 push_back 的时间复杂度是 O(n),其中 n 是链表的长度。对于一个以高效插入和删除为卖点的数据结构来说,这不是理想的设计选择。

总结重点区别表

特性 forward_list list
链表方向 单向 双向
迭代器 单向迭代器 双向迭代器
尾部插入 ❌ 无 ✅ 有
中间插入 .insert_after() .insert()
删除方式 .erase_after() .erase()
是否支持 .size() ❌ 无 ✅ 有
是否轻量 ❌ 略重

5. array<T, N>(C++11)

std::array 是 C++11 引入的 定长数组容器 ,在头文件 <array> 中定义,提供了类似 STL 容器的接口但底层是栈上固定大小数组

std::array<T, N> 是 C++ 中封装了原生数组的 STL 容器,大小固定、性能接近 C 数组,但支持标准容器接口。


构造函数

构造/初始化形式 说明 示例
默认构造 元素未初始化(内置类型无默认值,需注意) std::array<int, 3> a;
列表初始化(推荐) 直接初始化所有元素,大小固定必须匹配 std::array<int, 3> a = {1,2,3};
拷贝构造/赋值 支持,拷贝元素 std::array<int, 3> b(a);

常用 API 总览

类别 成员函数 说明
元素访问 at(i) 安全访问(越界抛异常)
operator[i] 非安全访问(无越界检查)
front() 返回首元素
back() 返回尾元素
data() 返回底层原始数组指针(T*)
容量 size() 返回元素数量(固定)
empty() 是否为空(即 size == 0
迭代器 begin(), end() 正向迭代器
rbegin(), rend() 反向迭代器
cbegin(), cend() const 版本
修改操作 fill(val) 全部元素填充为指定值
swap(other) 与另一个 std::array 交换内容

示例代码:快速上手

cpp 复制代码
#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // 元素访问
    std::cout << arr[0] << "\n";        // 1
    std::cout << arr.at(1) << "\n";     // 2(越界抛异常)
    std::cout << arr.front() << "\n";   // 1
    std::cout << arr.back() << "\n";    // 5

    // 容量
    std::cout << "size: " << arr.size() << "\n";    // 5
    std::cout << "empty: " << arr.empty() << "\n";  // false

    // 迭代器
    for (int x : arr) {
        std::cout << x << " ";   // 1 2 3 4 5
    }
    std::cout << "\n";

    // 修改操作
    arr.fill(9);
    for (int x : arr) std::cout << x << " ";  // 9 9 9 9 9
}

std::array vs std::vector

类别 std::vector std::array 差异说明
大小可变性 ✅ 动态变化 ❌ 固定大小 vector 支持增删元素,array 不支持
元素访问 operator[], at(), front(), back() 同上 元素访问接口一致
获取裸指针 .data() .data() 相同
容量相关 .size(), .capacity(), .empty(), .resize(), .reserve() .size(), .empty() array.capacity().resize()
修改操作 .push_back(), .pop_back(), .insert(), .erase() ❌ 无这些操作 array 不支持动态修改
元素填充 ❌ 无 .fill() .fill(val) array 独有
交换内容 .swap() .swap() 相同
迭代器 .begin(), .end(), rbegin(), rend() 同上 支持一致的 STL 迭代器接口
内存位置 堆上分配(new) 栈上分配 array 更轻量,vector 更灵活

关联式容器(自动排序,基于红黑树)

容器名 特点 键是否唯一 复杂度
set 自动排序的集合 ✅ 是 O(log n)
multiset 可重复元素集合 ❌ 否 O(log n)
map 键值对(key-value) ✅ 是 O(log n)
multimap 可重复键的 map ❌ 否 O(log n)
功能 / 容器 std::map std::multimap std::set std::multiset
键唯一性 否,允许重复键 否,允许重复元素
元素类型 pair<const Key, Value> pair<const Key, Value> Key Key
插入方式 insert, emplace, operator[] insert, emplace insert, emplace insert, emplace
访问元素 支持 operator[], at() 不支持 只支持查找 只支持查找
查找元素 find(key), count(key) (0/1) find(key), count(key) (≥0) find(val), count(val) (0/1) find(val), count(val) (≥0)
范围查询 lower_bound, upper_bound, equal_range map map map
删除元素 erase(key), erase(iterator) erase(key) 删除所有匹配元素 erase(value), erase(iterator) erase(value), erase(iterator)
元素排序 默认按键升序 默认按键升序 默认按元素升序 默认按元素升序
是否允许重复
是否支持下标
默认底层容器 红黑树 红黑树 红黑树 红黑树
典型用途 唯一键值映射 一键多值映射 唯一有序集合 允许重复元素的有序集合

std::pair

std::pair 是 C++ 标准库中一个非常基础的模板类,定义在 <utility> 头文件里。它的作用是 将两个类型可以不同的值"成对"存储起来,是构建简单复合数据结构的便捷工具。


主要作用

  • 成对存储两个数据 ,可以是不同类型,比如 intstd::string
  • 常用来表示键值对(比如 std::map 的元素类型就是 std::pair<const Key, Value>)。
  • 用于函数返回多个值。
  • 用作容器元素,方便存储关联的两个数据。

定义示例

cpp 复制代码
template <class T1, class T2>
struct pair {
    T1 first;
    T2 second;
};

典型用法示例

cpp 复制代码
#include <iostream>
#include <utility>  // std::pair
#include <string>

int main() {
    std::pair<int, std::string> p(1, "apple");

    std::cout << "first: " << p.first << "\n";   // 输出 1
    std::cout << "second: " << p.second << "\n"; // 输出 apple

    // 使用 std::make_pair 自动推导类型
    auto p2 = std::make_pair(2, 3.14);
    std::cout << p2.first << ", " << p2.second << "\n"; // 2, 3.14

    return 0;
}

常见操作

  • p.first:访问第一个元素
  • p.second:访问第二个元素
  • 比较运算符(==, < 等)基于成员逐个比较

在 STL 中的应用

  • std::mapstd::multimap 存储元素的类型是 std::pair<const Key, Value>

  • std::unordered_map 也是以 std::pair 表示元素

  • 迭代器解包常用结构绑定:

    cpp 复制代码
    for (const auto& [key, val] : myMap) {
        // 直接访问 key 和 val
    }

map

关联容器 API 对比:map vs multimap vs unordered_map

特性/容器 std::map std::multimap std::unordered_map
是否允许重复 key ❌ 否 ✅ 是 ❌ 否
key 排序方式 ✅ 按 key 升序(默认用 < ✅ 按 key 升序 ❌ 无序(基于哈希)
底层结构 平衡二叉搜索树(通常是红黑树) 平衡二叉搜索树 哈希表
平均时间复杂度 O(log n) O(log n) O(1)(最坏 O(n))
元素类型 pair<const Key, T> pair<const Key, T> pair<const Key, T>
访问元素方式 operator[], at() ❌ 不支持 [],只能用 insert/find operator[], at()
插入方式 insert, emplace, [] insert, emplace insert, emplace, []
删除方式 erase(key) / erase(iterator) 同上 同上
遍历顺序是否稳定/有序 ✅ 有序 ✅ 有序 ❌ 无序
边界操作支持 lower_bound, upper_bound equal_range, lower_bound, 等 ❌ 不支持边界操作
遍历所有重复 key 的值 不适用(key 唯一) equal_range(key) 不适用(key 唯一)
典型使用场景 一一映射、排序要求的数据结构 一对多映射(如成绩→学生) 快速哈希查找映射

示例代码对比

std::mapstd::unordered_map

std::mapstd::multimapstd::unordered_map插入遍历是一致的,不同的是 std::multimap有时候可能遍历某个键的所有元素,使用 equal_range。

equal_range也可以用在 std::mapstd::unordered_map,但是因为键是唯一的,没有必要。

cpp 复制代码
#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> mp;

    mp["apple"] = 3;
    mp["banana"] = 5;

    mp.insert({"orange", 7});    // 插入
    mp.emplace("pear", 2);       // 原地构造

    std::cout << mp["apple"] << "\n";     // 3

    if (mp.find("banana") != mp.end())
        std::cout << "found\n";

    mp.erase("orange");          // 删除元素
    std::cout << "C++17 及以上遍历:" << "\n";     
    
    //遍历元素 范围基 for(结构化绑定,C++17 及以上)
    for (const auto& [key, value] : mp) {
        std::cout << key << ": " << value << "\n";
    }
    std::cout << "传统迭代器遍历:" << "\n";     
    
    // 传统迭代器遍历写法
    for (auto it = mp.begin(); it != mp.end(); ++it) {
        std::cout << it->first << ": " << it->second << "\n";
    }

    return 0;
}

std::multimap

cpp 复制代码
#include <iostream>
#include <map>

int main() {
    std::multimap<std::string, int> mmap;

    mmap.insert({"apple", 3});
    mmap.insert({"apple", 4});  // 允许重复 key

    std::cout << "C++17 及以上遍历:" << "\n";
    // 遍历所有元素(包含重复键)
    for (const auto& [key, value] : mmap) {
        std::cout << key << ": " << value << "\n";
    }
    
    std::cout << "遍历某个键的所有元素:" << "\n";
    // 如果想遍历某个键的所有元素,使用 equal_range
    auto range = mmap.equal_range("apple");
    std::cout << "All values for key 'apple': ";
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->second << " ";
    }
    std::cout << "\n";
}

set

三种set对比

特性/容器 std::set std::multiset std::unordered_set
元素是否唯一 ✅ 是(自动去重) ❌ 否(允许重复) ✅ 是(自动去重)
元素是否有序 ✅ 是(默认升序) ✅ 是(默认升序) ❌ 否(无序)
底层结构 红黑树 红黑树 哈希表
时间复杂度(插/删/查) O(log n) O(log n) 平均 O(1),最坏 O(n)
插入方式 insert, emplace 同左 同左
删除方式 erase(val), erase(it) 同左 同左
查找方式 find(val), count(val) find(val), count(val) find(val), count(val)
范围操作(lower_bound 等) ✅ 支持 ✅ 支持 ❌ 不支持(哈希无顺序)
遍历顺序是否固定 ✅ 升序 ✅ 升序 ❌ 无序

示例代码

1. std::set 示例:唯一且自动升序

cpp 复制代码
#include <iostream>
#include <set>

int main() {
    std::set<int> s;

    s.insert(3);
    s.insert(1);
    s.insert(2);
    s.insert(2); // 重复元素不会插入

    for (int val : s) {
        std::cout << val << " ";  // 输出:1 2 3
    }

    if (s.count(2)) std::cout << "\nFound 2\n";

    s.erase(2); // 删除元素
}

2. std::multiset 示例:允许重复元素

cpp 复制代码
#include <iostream>
#include <set>

int main() {
    std::multiset<int> ms;

    ms.insert(2);
    ms.insert(1);
    ms.insert(2); // 允许插入重复元素

    for (int val : ms) {
        std::cout << val << " ";  // 输出:1 2 2
    }

    std::cout << "\nCount of 2: " << ms.count(2) << "\n"; // 输出 2

    ms.erase(ms.find(2)); // 只删一个 2
}

3. std::unordered_set 示例:唯一但无序

cpp 复制代码
#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_set<int> us;

    us.insert(2);
    us.insert(3);
    us.insert(1);
    us.insert(2); // 重复插入无效

    for (int val : us) {
        std::cout << val << " ";  // 输出顺序不确定
    }

    if (us.count(3)) std::cout << "\nFound 3\n";

    us.erase(1); // 删除元素
}

容器适配器(不是独立容器,是"封装器")

容器名 底层实现 特点
stack 默认用 deque 后进先出(LIFO)
queue 默认用 deque 先进先出(FIFO)
priority_queue 默认用 vector + make_heap 优先队列(最大堆/最小堆)

三种容器适配器对比

适配器 行为模型 默认底层容器 支持的接口
stack 后进先出(LIFO) deque push(), pop(), top()
queue 先进先出(FIFO) deque push(), pop(), front(), back()
priority_queue 最大堆(默认) vector push(), pop(), top(),可自定义比较器

std::stack

stack常用 API 一览表

函数名 说明 时间复杂度
push(const T& val) 将元素压入栈顶 O(1)
pop() 移除栈顶元素(不返回) O(1)
top() 返回栈顶元素(不移除) O(1)
empty() 判断栈是否为空 O(1)
size() 返回元素数量 O(1)
emplace(args...) 原地构造元素(C++11) O(1)
swap(other) 与另一个 stack 交换 O(1)

示例代码

cpp 复制代码
#include <iostream>
#include <stack>

int main() {
    std::stack<int> st;

    st.push(10);
    st.push(20);
    st.push(30);

    std::cout << "Top: " << st.top() << "\n";  // 30

    st.pop();
    std::cout << "Top after pop: " << st.top() << "\n";  // 20

    std::cout << "Size: " << st.size() << "\n";  // 2
    std::cout << "Is empty? " << std::boolalpha << st.empty() << "\n";  // false

    // 原地构造(适合自定义类型)
    std::stack<std::pair<int, std::string>> pst;
    pst.emplace(1, "Hello");
    std::cout << pst.top().second << "\n";  // Hello
}

注意事项

  1. pop() 不会返回值 ,只能先用 top() 查看再 pop()

    cpp 复制代码
    int x = st.pop();  // ❌ 编译错误
  2. 默认底层容器是 std::deque,也可以使用 std::vectorstd::list(但不推荐 list)。

    cpp 复制代码
    std::stack<int, std::vector<int>> vstack;

std::queue

std::queue 是 C++ STL 中的容器适配器 ,实现的是**先进先出(FIFO)**的队列行为。

  • 默认基于 std::deque 实现。
  • 只能从队尾添加元素 ,从队头移除元素
  • 不支持迭代器、下标访问等功能。

常用 API 总览

函数名 说明 时间复杂度
push(const T& val) 将元素加入队尾 O(1)
emplace(args...) 在队尾原地构造元素 O(1)
pop() 移除队头元素(不返回) O(1)
front() 返回队头元素(不移除) O(1)
back() 返回队尾元素 O(1)
empty() 判断是否为空 O(1)
size() 返回元素个数 O(1)
swap(other) 与另一个 queue 交换内容 O(1)

示例代码

cpp 复制代码
#include <iostream>
#include <queue>

int main() {
    std::queue<int> q;

    q.push(10);
    q.push(20);
    q.push(30);

    std::cout << "Front: " << q.front() << "\n";  // 10
    std::cout << "Back: " << q.back() << "\n";    // 30

    q.pop();  // 移除10
    std::cout << "New Front: " << q.front() << "\n";  // 20

    std::cout << "Size: " << q.size() << "\n";    // 2
    std::cout << "Empty? " << std::boolalpha << q.empty() << "\n";  // false
}

注意事项

  1. pop() 不返回值:

    cpp 复制代码
    int x = q.pop(); // ❌ 编译错误!

    需要先 front()pop()

    cpp 复制代码
    int x = q.front();
    q.pop();
  2. 不能访问中间元素或用下标:

    cpp 复制代码
    q[0];     // ❌ 错误
    for (auto it = q.begin(); ...)  // ❌ 错误
  3. 可以使用其他底层容器(如 list),但必须支持 front() / back() / push_back() / pop_front()

    cpp 复制代码
    std::queue<int, std::list<int>> q2;

std::priority_queue

std::priority_queue 是 C++ STL 中的容器适配器 ,实现的是一个堆(heap)结构的优先队列

  • 默认行为:大顶堆(最大元素优先出队)
  • 底层容器 :默认使用 std::vector
  • 排序工具 :基于 std::make_heap / push_heap / pop_heap 实现

常用 API 总览

函数名 功能 时间复杂度
push(const T& val) 插入元素 O(log n)
emplace(args...) 原地构造元素(C++11) O(log n)
pop() 移除优先级最高的元素 O(log n)
top() 查看优先级最高的元素 O(1)
empty() 判断是否为空 O(1)
size() 返回元素数量 O(1)
swap(other) 与另一个 priority_queue 交换内容 O(1)

示例代码(默认大顶堆)

cpp 复制代码
#include <iostream>
#include <queue>

int main() {
    std::priority_queue<int> pq;

    pq.push(10);
    pq.push(30);
    pq.push(20);

    std::cout << pq.top() << "\n"; // 30
    pq.pop();                      // 删除 30

    std::cout << pq.top() << "\n"; // 20
}

小顶堆写法(自定义比较器)

cpp 复制代码
#include <queue>
#include <vector>
#include <functional>

std::priority_queue<int, std::vector<int>, std::greater<>> minHeap;

minHeap.push(5);
minHeap.push(2);
minHeap.push(8);

// 最小值优先
std::cout << minHeap.top() << "\n";  // 2

存储结构说明

cpp 复制代码
template<
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>
> class priority_queue;
  • T: 元素类型
  • Container: 底层容器(默认 std::vector
  • Compare: 比较器(默认 std::less<T>,即大顶堆)

自定义类型示例

cpp 复制代码
struct Task {
    int priority;
    std::string name;
    bool operator<(const Task& other) const {
        return priority < other.priority;  // 大顶堆:priority 高的优先
    }
};

std::priority_queue<Task> taskQ;
taskQ.push({5, "compile"});
taskQ.push({10, "build"});
std::cout << taskQ.top().name;  // build

不支持的操作(适配器特性)

操作 是否支持 原因说明
下标访问 仅支持访问 top()
迭代器遍历 不支持遍历
排序顺序访问 每次只能访问"堆顶最大"元素

总结对比表

名称 行为模型 默认底层容器 主要接口 是否可自定义比较器
stack LIFO deque push, pop, top
queue FIFO deque push, pop, front, back
priority_queue vector push, pop, top ✅ 支持