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
相关推荐
水饺编程4 小时前
Windows 命令行:cd 命令1,cd 命令的简单使用
c语言·c++·windows·visual studio
一枝小雨4 小时前
【C++】深入解析C++嵌套依赖类型与typename关键字
开发语言·c++·笔记·学习笔记
水饺编程4 小时前
Windows 命令行:父目录与子目录
c语言·c++·windows·visual studio
大米粥哥哥6 小时前
Qt libcurl的下载、配置及简单测试 (windows环境)
开发语言·c++·windows·qt·http·curl·libcurl
闻缺陷则喜何志丹6 小时前
【逆序对 博弈】P10737 [SEERC 2020] Reverse Game|普及+
c++·算法·洛谷·博弈·逆序堆
大锦终6 小时前
【算法】哈希表专题
c++·算法·leetcode·哈希算法·散列表
蒹葭玉树6 小时前
【C++上岸】C++常见面试题目--数据结构篇(第十六期)
数据结构·c++·面试
睡不醒的kun7 小时前
leetcode算法刷题的第二十三天
数据结构·c++·算法·leetcode·职场和发展·贪心算法
mit6.8247 小时前
[re_2] rpc|http|nginx|protobuf|
网络·c++