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() 等内存操作;

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


构造函数

与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::list 和 std::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> 头文件里。它的作用是 将两个类型可以不同的值"成对"存储起来,是构建简单复合数据结构的便捷工具。


主要作用

  • 成对存储两个数据 ,可以是不同类型,比如 int 和 std::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::map 和 std::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::map 和std::unordered_map

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

equal_range也可以用在 std::map和std::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::vector、std::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 ✅ 支持