【数据结构手册008】STL容器完全参考指南

【数据结构手册008】STL容器完全参考指南

0 容器概览:统一接口,各异特性

C++ STL容器虽然功能各异,但遵循统一的设计哲学。理解这种"家族相似性"能让我们更快掌握新容器。

cpp 复制代码
// 所有容器的共同基础
template<typename T>
class ContainerConcept {
public:
    // 类型定义
    using value_type = T;
    using size_type = size_t;
    using iterator = ...;
    using const_iterator = ...;
    
    // 基础操作
    bool empty() const;
    size_type size() const;
    void clear();
    
    // 迭代器
    iterator begin();
    iterator end();
    const_iterator cbegin() const;
    const_iterator cend() const;
};

1 序列容器:线性存储

std::vector - 动态数组

本质:连续内存的动态数组

cpp 复制代码
#include <vector>

std::vector<int> vec = {1, 2, 3};

// === 核心操作 ===
vec.push_back(4);           // 尾部添加: {1,2,3,4}
vec.pop_back();             // 尾部删除: {1,2,3}
vec.insert(vec.begin() + 1, 9); // 指定位置插入: {1,9,2,3}
vec.erase(vec.begin() + 1);     // 指定位置删除: {1,2,3}

// === 元素访问 ===
int a = vec[1];             // 无边界检查: 2
int b = vec.at(1);          // 有边界检查: 2
int c = vec.front();        // 首元素: 1
int d = vec.back();         // 末元素: 3
int* data = vec.data();     // 原始指针

// === 容量管理 ===
vec.reserve(100);           // 预分配容量
vec.shrink_to_fit();        // 释放多余容量
size_t cap = vec.capacity(); // 当前容量
size_t sz = vec.size();     // 当前大小

// === 现代C++ ===
vec.emplace_back(5);        // 原地构造,避免拷贝

std::deque - 双端队列

本质:分段连续数组

cpp 复制代码
#include <deque>

std::deque<int> dq = {2, 3, 4};

// === 双端操作 ===
dq.push_front(1);           // 头部添加: {1,2,3,4}
dq.push_back(5);            // 尾部添加: {1,2,3,4,5}
dq.pop_front();             // 头部删除: {2,3,4,5}
dq.pop_back();              // 尾部删除: {2,3,4}

// === 元素访问 ===
int a = dq[1];              // 随机访问: 3
int b = dq.at(1);           // 安全访问: 3
int c = dq.front();         // 头部: 2
int d = dq.back();          // 尾部: 4

// === 修改操作 ===
dq.insert(dq.begin() + 1, 9); // 中间插入: {2,9,3,4}
dq.erase(dq.begin() + 2);      // 中间删除: {2,9,4}

std::list - 双向链表

本质:双向链表

cpp 复制代码
#include <list>

std::list<int> lst = {1, 3, 4};

// === 双端操作 ===
lst.push_front(0);          // 头部添加: {0,1,3,4}
lst.push_back(5);           // 尾部添加: {0,1,3,4,5}
lst.pop_front();            // 头部删除: {1,3,4,5}
lst.pop_back();             // 尾部删除: {1,3,4}

// === 高效插入删除 ===
auto it = lst.begin();
++it; // 指向1
lst.insert(it, 2);          // {1,2,3,4} - O(1)
it = lst.begin();
++++it; // 指向3
lst.erase(it);              // {1,2,4} - O(1)

// === 特殊操作 ===
lst.sort();                 // 排序: {1,2,4}
lst.unique();               // 去重
lst.reverse();              // 反转: {4,2,1}

// === 链表拼接 ===
std::list<int> other = {5, 6};
lst.splice(lst.end(), other); // {4,2,1,5,6}

std::forward_list - 单向链表

本质:单向链表(更省内存)

cpp 复制代码
#include <forward_list>

std::forward_list<int> flst = {2, 3, 4};

// === 单端操作 ===
flst.push_front(1);         // 头部添加: {1,2,3,4}
flst.pop_front();           // 头部删除: {2,3,4}

// === 特殊插入 ===
auto it = flst.begin();
++it; // 指向3
flst.insert_after(it, 9);   // {2,3,9,4} - 在指定位置后插入

// === 特殊删除 ===
it = flst.begin();
flst.erase_after(it);       // {2,9,4} - 删除指定位置后的元素

// 注意:没有size()、push_back()、pop_back()!

std::array - 固定大小数组

本质:编译期固定大小的数组

cpp 复制代码
#include <array>

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

// === 元素访问 ===
int a = arr[1];             // 2
int b = arr.at(1);          // 2
int c = arr.front();        // 1
int d = arr.back();         // 5

// === 容量信息 ===
bool empty = arr.empty();   // false
size_t size = arr.size();   // 5
size_t max_size = arr.max_size(); // 5

// === 填充操作 ===
arr.fill(0);                // {0,0,0,0,0}

// 注意:大小在编译期确定,无法改变!

2 容器适配器

std::stack - 后进先出

本质:LIFO栈

cpp 复制代码
#include <stack>

std::stack<int> stk;

// === 栈操作 ===
stk.push(1);                // 压栈: [1]
stk.push(2);                // [1,2]
stk.push(3);                // [1,2,3]

int top = stk.top();        // 查看栈顶: 3
stk.pop();                  // 出栈: [1,2]

bool empty = stk.empty();   // false
size_t size = stk.size();   // 2

// === 底层容器 ===
std::stack<int, std::vector<int>> stk_vec;   // 使用vector
std::stack<int, std::list<int>> stk_list;    // 使用list

std::queue - 先进先出

本质:FIFO队列

cpp 复制代码
#include <queue>

std::queue<int> q;

// === 队列操作 ===
q.push(1);                  // 入队: [1]
q.push(2);                  // [1,2]
q.push(3);                  // [1,2,3]

int front = q.front();      // 队首: 1
int back = q.back();        // 队尾: 3
q.pop();                    // 出队: [2,3]

bool empty = q.empty();     // false
size_t size = q.size();     // 2

std::priority_queue - 优先队列

本质:堆实现的优先级队列

cpp 复制代码
#include <queue>

// 默认大顶堆
std::priority_queue<int> pq;

// === 优先队列操作 ===
pq.push(3);                 // 添加: [3]
pq.push(1);                 // [3,1]
pq.push(4);                 // [4,3,1]
pq.push(2);                 // [4,3,2,1]

int top = pq.top();         // 最大元素: 4
pq.pop();                   // 删除最大: [3,2,1]

bool empty = pq.empty();    // false
size_t size = pq.size();    // 3

// === 小顶堆 ===
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(3);             // [3]
min_pq.push(1);             // [1,3]
min_pq.push(4);             // [1,3,4]
int min_top = min_pq.top(); // 最小元素: 1

3 关联容器

std::set - 唯一键有序集合

本质:红黑树实现的有序集合

cpp 复制代码
#include <set>

std::set<int> s = {3, 1, 4, 1, 5}; // {1,3,4,5}

// === 插入操作 ===
auto [it1, success1] = s.insert(2); // {1,2,3,4,5}, success1=true
auto [it2, success2] = s.insert(3); // 不变, success2=false

// === 查找操作 ===
auto it = s.find(3);        // 返回迭代器
if (it != s.end()) {
    int value = *it;        // 3
}

bool exists = s.contains(4); // C++20: true
int count = s.count(1);     // 1 (存在)

// === 范围操作 ===
auto lower = s.lower_bound(2); // 第一个>=2的元素
auto upper = s.upper_bound(4); // 第一个>4的元素

// === 删除操作 ===
s.erase(3);                 // 删除3: {1,2,4,5}
s.erase(it);                // 通过迭代器删除

std::multiset - 可重复键有序集合

本质:允许重复的有序集合

cpp 复制代码
#include <set>

std::multiset<int> ms = {1, 3, 3, 4};

// === 重复插入 ===
ms.insert(3);               // {1,3,3,3,4}

// === 计数操作 ===
int count = ms.count(3);    // 3

// === 范围查找 ===
auto range = ms.equal_range(3);
for (auto it = range.first; it != range.second; ++it) {
    std::cout << *it << " "; // 3 3 3
}

std::unordered_set - 唯一键无序集合

本质:哈希表实现的无序集合

cpp 复制代码
#include <unordered_set>

std::unordered_set<int> us = {3, 1, 4, 1, 5}; // {1,3,4,5} 顺序不定

// === 基本操作 ===
us.insert(2);               // 插入2
us.erase(3);                // 删除3
bool found = us.contains(4); // 检查存在

// === 哈希表管理 ===
us.reserve(100);            // 预分配桶
float load_factor = us.load_factor(); // 当前负载因子
size_t bucket_count = us.bucket_count(); // 桶数量

// === 桶迭代 ===
for (size_t i = 0; i < us.bucket_count(); ++i) {
    size_t bucket_size = us.bucket_size(i);
}

std::unordered_multiset - 可重复键无序集合

本质:允许重复的无序集合

cpp 复制代码
#include <unordered_set>

std::unordered_multiset<int> ums = {1, 3, 3, 4};

// === 重复操作 ===
ums.insert(3);              // 再添加一个3
int count = ums.count(3);   // 3

auto range = ums.equal_range(3); // 所有等于3的元素

4 关联数组

std::map - 唯一键有序映射

本质:红黑树实现的有序键值对

cpp 复制代码
#include <map>

std::map<std::string, int> scores = {
    {"Alice", 95},
    {"Bob", 87}
};

// === 插入操作 ===
scores["Charlie"] = 92;     // 插入或更新
auto [it, success] = scores.insert({"David", 78});

// === 访问操作 ===
int alice_score = scores["Alice"];    // 95
int bob_score = scores.at("Bob");     // 87

// === 查找操作 ===
auto find_it = scores.find("Alice");
if (find_it != scores.end()) {
    auto& [key, value] = *find_it;    // 结构化绑定
}

bool has_eve = scores.contains("Eve"); // false

// === 范围操作 ===
auto lower = scores.lower_bound("B"); // 第一个键>=B
auto upper = scores.upper_bound("C"); // 第一个键>C

// === 现代插入 ===
scores.emplace("Eve", 88);           // 原地构造
scores.try_emplace("Frank", 91);     // 键不存在时插入
scores.insert_or_assign("Alice", 96); // 插入或更新

std::multimap - 可重复键有序映射

本质:允许重复键的有序映射

cpp 复制代码
#include <map>

std::multimap<std::string, int> mmap = {
    {"Alice", 95},
    {"Alice", 88},
    {"Bob", 87}
};

// === 重复插入 ===
mmap.insert({"Alice", 92}); // 允许重复键

// === 范围查找 ===
auto range = mmap.equal_range("Alice");
for (auto it = range.first; it != range.second; ++it) {
    std::cout << it->second << " "; // 95 88 92
}

std::unordered_map - 唯一键无序映射

本质:哈希表实现的无序键值对

cpp 复制代码
#include <unordered_map>

std::unordered_map<std::string, int> word_count;

// === 基本操作 ===
word_count["hello"] = 3;              // 插入或更新
word_count["world"]++;                // 更新
word_count.erase("hello");            // 删除

// === 安全访问 ===
if (word_count.find("test") != word_count.end()) {
    int count = word_count["test"];   // 安全访问
}

// === 哈希表管理 ===
word_count.reserve(1000);             // 预分配
word_count.rehash(500);               // 重新哈希

// === 现代操作 ===
word_count.emplace("new", 1);         // 原地构造
word_count.try_emplace("key", 42);    // 安全插入

std::unordered_multimap - 可重复键无序映射

本质:允许重复键的无序映射

cpp 复制代码
#include <unordered_map>

std::unordered_multimap<std::string, int> ummap;

// === 重复插入 ===
ummap.insert({"key", 1});
ummap.insert({"key", 2});             // 允许重复

// === 范围操作 ===
auto range = ummap.equal_range("key");
for (auto it = range.first; it != range.second; ++it) {
    std::cout << it->second << " ";   // 1 2
}

5 字符串

std::string - 字符序列

本质:动态字符数组

cpp 复制代码
#include <string>

std::string str = "Hello";

// === 修改操作 ===
str += " World";            // 拼接: "Hello World"
str.append("!");            // 追加: "Hello World!"
str.insert(5, ",");         // 插入: "Hello, World!"
str.erase(5, 1);            // 删除: "Hello World!"

// === 查找操作 ===
size_t pos = str.find("World");     // 6
size_t rpos = str.rfind("l");       // 9 (最后一个l)

// === 子串操作 ===
std::string sub = str.substr(6, 5); // "World"
str.replace(6, 5, "C++");           // "Hello C++!"

// === 数值转换 ===
std::string num_str = std::to_string(42); // "42"
int num = std::stoi("123");         // 123

// === C字符串交互 ===
const char* cstr = str.c_str();     // C风格字符串
size_t len = str.length();          // 字符串长度

位集合:紧凑的布尔数组

std::bitset - 固定大小位集合

本质:编译期固定大小的位数组

cpp 复制代码
#include <bitset>

std::bitset<8> bs(0b10101010); // 170

// === 位操作 ===
bs.set(0);                    // 设置第0位: 0b10101011
bs.reset(1);                  // 清除第1位: 0b10101001
bs.flip(2);                   // 翻转第2位: 0b10101101

// === 访问操作 ===
bool bit0 = bs[0];            // 访问第0位
bool bit1 = bs.test(1);       // 安全访问第1位

// === 统计操作 ===
size_t count = bs.count();    // 设置位数量: 5
bool any = bs.any();          // 是否有设置位: true
bool all = bs.all();          // 是否所有位都设置: false
bool none = bs.none();        // 是否没有设置位: false

// === 转换操作 ===
std::string str = bs.to_string(); // "10101101"
unsigned long num = bs.to_ulong(); // 173

性能特征快速参考

时间复杂度对比表

操作 vector deque list set unordered_set
随机访问 O(1) O(1) O(n) O(log n) O(1) avg
头部插入 O(n) O(1) O(1) - -
尾部插入 O(1) O(1) O(1) - -
中间插入 O(n) O(n) O(1) O(log n) O(1) avg
查找 O(n) O(n) O(n) O(log n) O(1) avg

迭代器失效规则

容器 插入操作 删除操作
vector 所有迭代器可能失效 被删元素之后的迭代器失效
deque 除首尾插入外可能失效 除首尾删除外可能失效
list 不会失效 只有被删元素迭代器失效
关联容器 不会失效 只有被删元素迭代器失效

选择决策树

基础选择流程

复制代码
需要键值对?
├─ 是 → 需要有序?
│   ├─ 是 → 键是否唯一?
│   │   ├─ 是 → std::map
│   │   └─ 否 → std::multimap
│   └─ 否 → 键是否唯一?
│       ├─ 是 → std::unordered_map  
│       └─ 否 → std::unordered_multimap
│
└─ 否 → 需要唯一元素?
    ├─ 是 → 需要有序?
    │   ├─ 是 → std::set
    │   └─ 否 → std::unordered_set
    │
    └─ 否 → 主要操作在首尾?
        ├─ 是 → std::deque
        ├─ 否 → 需要随机访问?
        │   ├─ 是 → std::vector
        │   └─ 否 → std::list
        │
        └─ 特定访问模式?
            ├─ LIFO → std::stack
            ├─ FIFO → std::queue
            └─ 优先级 → std::priority_queue

性能优化指南

cpp 复制代码
// 1. 预分配空间
std::vector<int> vec;
vec.reserve(1000);           // 避免重复分配

std::unordered_map<int, int> umap;
umap.reserve(1000);          // 避免重新哈希

// 2. 使用emplace避免拷贝
std::vector<std::string> vec;
vec.emplace_back("hello");   // 原地构造

std::map<int, std::string> map;
map.emplace(1, "world");     // 避免临时对象

// 3. 利用移动语义
std::vector<std::string> getData() {
    std::vector<std::string> data;
    // ... 填充数据
    return data;             // 移动而非拷贝
}

// 4. 选择合适的算法
std::vector<int> data = {5, 3, 1, 4, 2};
std::sort(data.begin(), data.end());     // 快速排序
std::stable_sort(data.begin(), data.end()); // 稳定排序

// 5. 利用视图避免拷贝 (C++20)
#if __has_include(<ranges>)
#include <ranges>
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto even_numbers = numbers | std::views::filter([](int n) { 
    return n % 2 == 0; 
}); // 不拷贝数据
#endif

现代C++最佳实践

1. 结构化绑定 (C++17)

cpp 复制代码
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};

// 传统方式
for (const auto& pair : scores) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

// 现代方式
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << std::endl;
}

2. 透明比较器 (C++14)

cpp 复制代码
std::set<std::string, std::less<>> transparent_set;

transparent_set.insert("hello");
bool found = transparent_set.contains("hello"); // 不需要构造std::string

3. 安全访问模式

cpp 复制代码
std::optional<int> safeGet(const std::map<std::string, int>& map, 
                          const std::string& key) {
    auto it = map.find(key);
    if (it != map.end()) {
        return it->second;
    }
    return std::nullopt;
}

// 使用
if (auto value = safeGet(scores, "Alice")) {
    std::cout << "Score: " << *value << std::endl;
}

总结:STL容器的设计哲学

  1. RAII原则:自动管理资源生命周期
  2. 泛型编程:模板实现类型无关算法
  3. 迭代器抽象:统一访问接口
  4. 算法与容器分离:通过迭代器连接
  5. 异常安全:提供基本异常安全保证

掌握这些容器不仅意味着知道它们的API,更重要的是理解它们的设计思想和适用场景。在实际开发中,正确的容器选择往往比算法优化带来更大的性能提升。

相关推荐
kingmax542120082 小时前
《数据结构C语言:单向链表-链表基本操作(尾插法建表、插入)》15分钟试讲教案【模版】
c语言·数据结构·链表
AI科技星2 小时前
质量定义方程常数k = 4π m_p的来源、推导与意义
服务器·数据结构·人工智能·科技·算法·机器学习·生活
jllllyuz2 小时前
matlab使用B样条进行曲线曲面拟合
开发语言·matlab
农夫山泉2号3 小时前
【c++】——c++编译的so中函数有额外的字符
java·服务器·c++
Fine姐3 小时前
数据结构04——二叉树搜索树BST
数据结构
ku_code_ku3 小时前
python bert_score使用本地模型的方法
开发语言·python·bert
小马哥编程3 小时前
【软考架构】滑动窗口限流算法的原理是什么?
java·开发语言·架构
仰泳的熊猫3 小时前
1077 Kuchiguse
数据结构·c++·算法·pat考试
云栖梦泽3 小时前
鸿蒙数据持久化实战:构建本地存储与云同步系统
开发语言·鸿蒙系统