C++之常用容器

C++ 标准模板库(STL)中的容器是 C++ 编程的基石。掌握它们是高效编程的关键。

下面我将详细介绍几个最重要和最常用的容器,并将它们分为三类:序列容器关联容器容器适配器

一、序列容器 (Sequence Containers)

这类容器中的元素是线性排列的,就像一个队伍。你可以通过元素在队伍中的位置(索引)来访问它们。

1. std::vector:动态数组(最重要的容器

  • 一句话总结 :C++ 的"瑞士军刀",当你不知道用什么容器时,就用 vector

  • 核心特性

    • 底层结构:一块连续的内存,和普通数组一样。
    • 访问 :极快的随机访问速度。通过索引 vec[i] 访问元素是 O(1) 操作。
    • 尾部操作 :在末尾添加 (push_back) 或删除 (pop_back) 元素非常快(均摊 O(1))。
    • 中间操作:在中间或开头插入/删除元素很慢(O(N)),因为它需要移动之后的所有元素。
  • 适用场景

    • 需要频繁地随机访问元素。
    • 主要在容器末尾进行添加/删除操作。
    • 作为函数参数或返回值传递一组数据。
  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <vector>
    #include <string>
    
    int main() {
        // 创建一个存储字符串的 vector
        std::vector<std::string> names;
    
        // 1. 添加元素 (在尾部)
        names.push_back("Alice");
        names.push_back("Bob");
        names.push_back("Charlie");
    
        // 2. 随机访问元素 (O(1))
        std::cout << "The first name is: " << names[0] << std::endl;
        names[1] = "Robert"; // 修改元素
    
        // 3. 遍历容器
        std::cout << "\nAll names:" << std::endl;
        for (const std::string& name : names) {
            std::cout << "- " << name << std::endl;
        }
    
        // 4. 在中间插入 (O(N),较慢)
        names.insert(names.begin() + 1, "Eve"); // 在索引 1 处插入
    
        // 5. 删除元素
        names.pop_back(); // 删除最后一个元素 "Charlie"
    
        std::cout << "\nAfter modifications:" << std::endl;
        for (const auto& name : names) {
            std::cout << "- " << name << std::endl;
        }
    
        std::cout << "Current size: " << names.size() << std::endl;
    }

2. std::list:双向链表

  • 一句话总结:为频繁的插入和删除操作而生。

  • 核心特性

    • 底层结构:非连续的双向链表,每个元素都指向前一个和后一个元素。
    • 访问 :不支持快速随机访问。要访问第 i 个元素,必须从头或尾开始遍历 i 步(O(N))。
    • 任意位置操作:在任何位置插入或删除元素都非常快(O(1)),只要你已经有了指向该位置的迭代器。
    • 迭代器稳定性:插入或删除元素不会使指向其他元素的迭代器失效。
  • 适用场景

    • 需要对容器进行大量的插入和删除操作,尤其是在中间位置。
    • 不需要随机访问。
  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <list>
    
    int main() {
        std::list<int> numbers;
        numbers.push_back(10);
        numbers.push_back(20);
        numbers.push_front(5); // [5, 10, 20]
    
        auto it = numbers.begin();
        it++; // it 指向 10
    
        // 在 10 的前面插入 8 (O(1))
        numbers.insert(it, 8); // [5, 8, 10, 20]
    
        // 删除 10 (O(1))
        numbers.erase(it); // [5, 8, 20]
    
        std::cout << "List content:" << std::endl;
        for (int n : numbers) {
            std::cout << n << " ";
        }
        std::cout << std::endl;
    }

二、关联容器 (Associative Containers)

这类容器自动对其元素进行排序 ,并允许通过键 (Key) 来快速查找。

3. std::map:有序键值对

  • 一句话总结:按键排序的字典。

  • 核心特性

    • 底层结构 :通常是红黑树(一种自平衡二叉搜索树)。
    • 元素 :存储 std::pair<const Key, Value> 形式的键值对。
    • 排序 :所有元素都根据键 Key 自动排序。
    • 查找:基于键的查找、插入和删除速度都很快(O(log N))。
  • 适用场景

    • 需要存储键值对。
    • 需要数据始终按键排序。
    • 需要能够按范围查找(例如,查找所有在 'A' 和 'C' 之间的键)。
  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <map>
    #include <string>
    
    int main() {
        std::map<std::string, int> ages;
    
        // 插入元素
        ages["Charlie"] = 30;
        ages["Alice"] = 25;
        ages["Bob"] = 35;
    
        // 查找和访问
        std::cout << "Alice's age: " << ages["Alice"] << std::endl;
    
        // 检查键是否存在
        if (ages.count("David")) {
            std::cout << "David's age is " << ages["David"] << std::endl;
        } else {
            std::cout << "David is not in the map." << std::endl;
        }
    
        // 遍历 (注意输出是按键排序的:Alice, Bob, Charlie)
        std::cout << "\nAll ages (sorted by name):" << std::endl;
        for (const auto& pair : ages) {
            std::cout << pair.first << " is " << pair.second << " years old." << std::endl;
        }
    }

4. std::unordered_map:无序键值对(非常常用

  • 一句话总结:性能更高的字典,但没有排序。

  • 核心特性

    • 底层结构哈希表 (Hash Table)
    • 元素:存储键值对。
    • 排序:元素是无序的,遍历顺序不确定。
    • 查找:基于键的查找、插入和删除速度极快(平均 O(1))。
  • 适用场景

    • 需要存储键值对,且追求极致的查找性能。
    • 不需要数据排序。这是绝大多数字典应用场景的首选。
  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <unordered_map>
    #include <string>
    
    int main() {
        std::unordered_map<std::string, int> ages;
    
        ages["Charlie"] = 30;
        ages["Alice"] = 25;
        ages["Bob"] = 35;
    
        std::cout << "Bob's age: " << ages["Bob"] << std::endl;
    
        // 遍历 (输出顺序不确定)
        std::cout << "\nAll ages (in arbitrary order):" << std::endl;
        for (const auto& pair : ages) {
            std::cout << pair.first << " is " << pair.second << " years old." << std::endl;
        }
    }

std::setstd::unordered_set
set 容器可以看作是只有键没有值的 map。它们用于存储唯一的、不重复的元素集合。

  • std::set:有序集合,基于红黑树,O(log N) 操作。
  • std::unordered_set:无序集合,基于哈希表,平均 O(1) 操作。
    它们非常适合用于去重或快速检查某个元素是否存在。

三、容器适配器 (Container Adapters)

它们不是真正的容器,而是对现有序列容器(如 vector, deque, list)的封装,提供了特定的接口。

5. std::stack:栈

  • 一句话总结:后进先出 (LIFO - Last-In, First-Out) 的数据结构。

  • 核心特性 :只能在"顶部"添加 (push) 和移除 (pop) 元素。

  • 适用场景:函数调用栈、括号匹配、撤销/重做功能。

  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <stack> // 包含头文件
    
    int main() {
        std::stack<int> s;
        s.push(1); // [1]
        s.push(2); // [1, 2]
        s.push(3); // [1, 2, 3]
    
        std::cout << "Top element: " << s.top() << std::endl; // 输出 3
    
        s.pop(); // 移除 3, 栈变为 [1, 2]
        std::cout << "Top element after pop: " << s.top() << std::endl; // 输出 2
    
        std::cout << "Is stack empty? " << (s.empty() ? "Yes" : "No") << std::endl;
    }

6. std::queue:队列

  • 一句话总结:先进先出 (FIFO - First-In, First-Out) 的数据结构。

  • 核心特性 :在队尾 (push) 添加元素,在队头 (pop) 移除元素。

  • 适用场景:任务队列、广度优先搜索 (BFS)、打印机任务。

  • 代码示例

    cpp 复制代码
    #include <iostream>
    #include <queue> // 包含头文件
    
    int main() {
        std::queue<std::string> q;
        q.push("Task 1");
        q.push("Task 2");
        q.push("Task 3");
    
        std::cout << "Front element: " << q.front() << std::endl; // 输出 Task 1
        std::cout << "Back element: " << q.back() << std::endl;  // 输出 Task 3
    
        q.pop(); // 移除 Task 1
        std::cout << "Front element after pop: " << q.front() << std::endl; // 输出 Task 2
    }

总结与如何选择

容器 底层结构 排序性 随机访问 中间插入/删除 主要用途
std::vector 动态数组 按插入顺序 O(1) O(N) 默认首选,通用数据存储
std::list 双向链表 按插入顺序 O(N) O(1) 频繁的任意位置插入/删除
std::map 红黑树 按键排序 N/A O(log N) 有序的键值对存储
std::unordered_map 哈希表 无序 N/A O(1) 高性能的键值对查找
std::set 红黑树 按键排序 N/A O(log N) 有序的唯一元素集合
std::unordered_set 哈希表 无序 N/A O(1) 高性能的唯一元素查找
std::stack vectordeque LIFO N/A N/A 后进先出场景
std::queue deque FIFO N/A N/A 先进先出场景

简单选择流程:

  1. 需要键值对吗?

    • 是 -> 需要排序吗?

      • 是 -> std::map
      • 否 -> std::unordered_map (通常是更好的选择)
  2. 不需要键值对,只需要存储一串元素?

    • 需要快速随机访问吗?(用 [] 访问)

      • 是 -> std::vector (绝大多数情况)
    • 需要频繁在中间插入/删除吗?

      • 是 -> std::list
  3. 需要后进先出 (LIFO) 或先进先出 (FIFO) 的简单接口吗?

    • 是 -> std::stackstd::queue
相关推荐
ysa05103015 小时前
虚拟位置映射(标签鸽
数据结构·c++·笔记·算法
m0_7482480215 小时前
C++中的位运算符:与、或、异或详解
java·c++·算法
草莓熊Lotso16 小时前
C++ 方向 Web 自动化测试实战:以博客系统为例,从用例到报告全流程解析
前端·网络·c++·人工智能·后端·python·功能测试
共享家952717 小时前
LRU 缓存的设计与实现
开发语言·c++
草莓熊Lotso17 小时前
Linux 基础开发工具入门:软件包管理器的全方位实操指南
linux·运维·服务器·c++·人工智能·网络协议·rpc
小龙报17 小时前
算法通关指南:数据结构和算法篇 --- 队列相关算法题》--- 1. 【模板】队列,2. 机器翻译
c语言·开发语言·数据结构·c++·算法·学习方法·visual studio
晨非辰18 小时前
【数据结构初阶】--从排序算法原理分析到代码实现操作,参透插入排序的奥秘!
c语言·开发语言·数据结构·c++·算法·面试·排序算法
2301_795167201 天前
玩转Rust高级应用 如何避免对空指针做“解引用”操作,在C/C++ 里面就是未定义行为
c语言·c++·rust
不染尘.1 天前
2025_11_7_刷题
开发语言·c++·vscode·算法
似水এ᭄往昔1 天前
【C++】--stack和queue
开发语言·c++