C++ 容器全解析:从基础到实战的体系化教程(史上最全面的C++容器教程)

一、容器的本质与学习价值(什么是容器?为什么要学它?)

在编程时,我们常会遇到需要处理批量数据的场景——可能是一组数字、一串字符串,也可能是多个自定义对象。这时如果直接用零散的变量来存储,不仅会让代码充斥着重复的声明与赋值,还得手动编写循环、判断等逻辑来处理数据的增删、查找,既繁琐又容易出错。

C++ 的 “容器” 正是为解决这类问题设计的:它是标准库(STL)提供的数据结构模板,通过封装数据的存储逻辑和操作方法,让开发者得以从 “如何存储数据” 的细节中解放出来,专注于 “如何使用数据”。无需手动实现链表、哈希表等复杂底层结构,只需调用容器提供的现成接口,就能轻松完成数据的增删查改。

简单说:容器 = 数据的 “存储空间” + 预设的 “管理工具”(自动处理增删查改)。容器是 “数据结构的工业化实现”,比如用vector存班级成绩,不用手动分配数组大小;用map存通讯录,不用写循环查找联系人 —— 这些细节容器都帮我们做好了。

学习容器的核心价值:

  • 提高开发效率:用成熟的容器替代重复开发,减少代码量和出错概率;不用重复造轮子,不用自己手写链表、哈希表这些复杂数据结构;
  • 保证程序性能:标准库容器经过严格优化、千锤百炼,执行效率远高于手写数据结构,比自己写的代码运行更快;
  • 适配实战需求和各种场景:所有 C++ 项目都会用到容器,是进阶编程的必备基础;同时不管是存数据、查数据还是按规则处理数据,都有对应的容器可用。
     

容器本质上是 C++ 标准库中定义的模板类(template class)。具体来说:它们是用类模板实现的,因此能存储各种类型的元素(比如vector<int>存储整数,list<string>存储字符串,甚至map<int, MyClass>存储自定义对象)。作为类,它们封装了数据(元素的存储)和操作数据的方法(如push_backfindsize等成员函数),符合面向对象的设计思想。开发者通过创建容器类的实例(如vector<int> nums;)来使用它们,就像使用自定义类的对象一样。简单说,容器是 “能存储批量数据的通用类”,模板特性让它们能适配各种数据类型,而类的封装性让它们能提供统一、易用的操作接口。

容器的设计意义在于为开发者提供高效、通用、可靠的数据管理方案,解决批量数据存储和操作的核心问题。具体来说,它的价值体现在三个维度:

1. 屏蔽底层复杂性,降低开发成本
计算机处理批量数据时,需要底层数据结构(如数组、链表、哈希表、树等)的支撑,但这些结构的实现细节复杂且容易出错(比如手动管理链表指针、处理哈希冲突、实现红黑树平衡等)。
容器通过封装底层细节,将复杂的数据结构转化为简单的接口(如push_back添加元素、find查找元素),让开发者无需关心 “数据如何存储”,只需专注 “如何使用数据”。例如:
vector时,不用手动实现动态扩容的数组; 用map时,不用自己编写红黑树来保证有序性; 用unordered_map时,不用处理哈希函数设计和冲突解决。
这相当于 “把复杂的轮子做好,让开发者直接用轮子造车”,大幅减少重复劳动和出错概率。

2. 统一操作接口,提升代码一致性
不同数据结构的原生操作方式差异很大(比如数组用下标访问,链表用指针遍历,树用递归查找),但容器通过标准化接口(如begin()/end()遍历、size()获取长度、insert()插入元素),让所有容器的使用逻辑保持一致。
这种一致性带来两个好处:降低学习成本:学会一套通用规则,就能操作所有容器(比如用范围 for 循环遍历vectorlistset的语法完全相同); 提高代码可读性:无论使用哪种容器,其他开发者都能通过熟悉的接口快速理解代码逻辑。
每种容器都在 “易用性” 和 “性能” 之间找到平衡 —— 既保证接口简单,又通过底层优化(如vector的连续内存、unordered_map的哈希表)确保高效运行。

3. 适配场景需求,平衡性能与易用性
不同业务场景对数据操作的需求不同:有的需要快速按序号访问(如学生成绩表);有的需要频繁在中间插入删除(如日程安排);有的需要通过关键词快速查找(如通讯录);有的需要按优先级处理(如任务调度)。
容器通过多样化的设计,为每种场景提供最优解:用vector满足 “快速下标访问 + 尾部操作”;用list满足 “频繁中间增删”;用unordered_map满足 “快速键值查找”;用priority_queue满足 “优先级排序”。
每种容器都在 “易用性” 和 “性能” 之间找到平衡 —— 既保证接口简单,又通过底层优化(如vector的连续内存、unordered_map的哈希表)确保高效运行。

一句话总结,容器是 “数据结构的工业化实现”:它把程序员从重复开发底层结构的工作中解放出来,用统一、可靠的方式管理数据,让开发者能更专注于业务逻辑,同时保证程序的性能和可维护性。这也是为什么容器成为 C++ 标准库的核心 —— 它是 “高效编程” 的基础设施。

    二、容器的分类体系(容器家族图谱,一张图看懂所有成员)

    C++ 容器按功能可分为三大类,就像三个工具套装,各有各的专长。每类都有明确的适用场景:

    容器
    ├─ 顺序容器(按插入顺序存储,元素有位置关系)
    │  ├─ vector:动态数组(最常用,采用下标访问高效,访问快)
    │  ├─ deque:双端队列(两端插入/删除效率高,操作快)
    │  ├─ list:双向链表(任意位置插入/删除高效)
    │  └─ forward_list:单向链表(轻量版,省内存,仅支持单向遍历)
    │
    ├─ 关联容器(按“键”存储元素,支持快速查找)
    │  ├─ 有序关联容器(基于红黑树,自动排序)
    │  │  ├─ set:元素唯一,按值排序(集合)
    │  │  ├─ map:键值对,键唯一,按键排序(映射)
    │  │  ├─ multiset:元素可重复,按值排序
    │  │  └─ multimap:键可重复,按键排序
    │  └─ 无序关联容器(基于哈希表,不排序)
    │     ├─ unordered_set:元素唯一,哈希存储(查找更快)
    │     └─ unordered_map:键值对,键唯一,哈希存储(查找更快)
    │
    └─ 容器适配器(简化版容器,适配特定场景:简化接口的专用容器,基于其他容器实现)
       ├─ stack:栈(先进后出,LIFO)
       ├─ queue:队列(先进先出,FIFO)
       └─ priority_queue:优先队列(按优先级出队)
    

    三、容器通用语法基础(所有容器都认的规则)

    所有容器都遵循以下基本规则,是使用容器的 “通用语言”:

    1. 头文件与命名空间

    • 必须包含对应容器的头文件(如#include <vector>#include <map>);
    • 容器属于std命名空间,使用时需加std::前缀(或通过using namespace std;简化)。
    #include <vector>   // 包含vector头文件
    using namespace std; // 后续使用容器可省略std::
    

    2. 容器的定义与初始化

    基本定义格式
    容器类型<元素类型> 容器名; 
    // 示例:vector<int> scores; // 定义存储int类型的vector,名为scores
    
    常用初始化方式(以 vector 为例)
    初始化方式示例代码说明
    默认初始化vector<int> v;创建空容器,后续可动态添加元素
    列表初始化vector<int> v = {1, 2, 3};用初始化列表赋值(C++11 及以上支持)
    数量 + 初始值vector<int> v(5, 0);创建含 5 个元素的容器,每个元素初始化为 0
    复制初始化vector<int> v2(v1);复制 v1 的所有元素到 v2
    范围初始化vector<int> v(v1.begin(), v1.end());复制 v1 中从 begin 到 end 的元素

    3. 迭代器:容器的 “遍历工具”

    迭代器是访问容器元素的 “通用指针”,所有容器都支持通过迭代器遍历元素。

    迭代器定义
    // 可修改元素的迭代器
    容器类型<元素类型>::iterator 迭代器名; 
    // 只读迭代器(不能修改元素)
    容器类型<元素类型>::const_iterator 迭代器名; 
    
    迭代器核心操作
    • begin():返回指向容器第一个元素的迭代器;
    • end():返回指向 “最后一个元素后一位” 的迭代器(作为遍历结束标记);
    • *it:访问迭代器指向的元素;
    • ++it:迭代器向后移动一位(指向 next 元素)。
    遍历示例
    vector<int> v = {1, 2, 3};
    
    // 方式1:普通迭代器(可修改元素)
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        *it *= 2; // 修改元素为2, 4, 6
    }
    
    // 方式2:auto简化迭代器类型(C++11及以上)
    for (auto it = v.begin(); it != v.end(); ++it) {
        cout << *it << " "; // 输出:2 4 6
    }
    
    // 方式3:范围for循环(最简洁,C++11及以上)
    for (int num : v) {
        cout << num << " "; // 输出:2 4 6
    }
    

    4. 容器通用成员函数

    所有容器都支持的基础操作:

    函数名功能描述示例(vector<int> v)
    size()返回容器中元素的个数int n = v.size();
    empty()判断容器是否为空(空则返回 true)if (v.empty()) { /* 容器为空时的操作 */ }
    clear()清空容器中所有元素(size 变为 0)v.clear();
    begin()/end()返回指向首元素 / 尾后位置的迭代器auto it = v.begin();


    四、顺序容器详解(像 “排队的盒子”,数据按插入顺序排列)

    顺序容器的元素按插入顺序排列,有明确的 “位置” 概念,适合按顺序处理数据。

    特点:数据有明确的先后顺序,就像排队一样,每个元素有自己的位置(下标)。

    1. vector(动态数组)

    相当于 “可伸缩的抽屉”。一开始可以指定大小,不够了会自动扩容。
    ✅ 优点:通过下标(比如vec[0])访问元素特别快,适合频繁读数据、少改动的场景。
    ❌ 缺点:如果在中间插入 / 删除元素,后面的元素都要挪动位置,效率低。
    比如:存班级同学的成绩,查分数时直接按学号(下标)找,很方便。

    核心特点
    • 内存连续(类似可自动扩容的数组);
    • 支持下标访问(v[i]),效率极高(O (1));
    • 尾部插入 / 删除(push_back/pop_back)高效(O (1));
    • 中间 / 头部插入 / 删除低效(需挪动元素,O (n))。
    专属语法与操作
    操作函数 / 语法示例代码说明
    元素访问下标运算符 /at()v[0] = 10; v.at(1) = 20;at()会检查下标是否越界,越界时抛出异常
    访问首尾元素front()/back()int first = v.front();快速获取第一个 / 最后一个元素
    尾部添加元素push_back(值)v.push_back(30);在容器末尾新增一个元素
    尾部删除元素pop_back()v.pop_back();删除容器最后一个元素(无返回值)
    任意位置插入insert(迭代器, 值)v.insert(v.begin() + 1, 15);在迭代器指向的位置插入元素
    任意位置删除erase(迭代器)v.erase(v.begin());删除迭代器指向的元素
    调整容器大小resize(新大小)v.resize(5);若新大小大于当前 size,新增元素用默认值填充
    容量管理capacity()/reserve()v.reserve(100);capacity()返回当前容量,reserve(n)预留至少 n 个元素的容量(避免频繁扩容)
    与数组的转换

    vector 内存连续,可直接转换为 C 风格数组:

    vector<int> v = {1, 2, 3};
    int* arr = v.data(); // 获取首元素地址(等价于&v[0])
    // 此时arr可作为普通数组使用(如arr[0] = 10 → v[0]也会变为10)
    
    完整示例
    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main() {
        vector<int> v = {1, 2, 3}; // 初始化:[1, 2, 3]
        
        v.push_back(4); // 尾部添加:[1, 2, 3, 4]
        v.insert(v.begin() + 2, 9); // 在位置2插入9:[1, 2, 9, 3, 4]
        v.erase(v.begin() + 1); // 删除位置1的元素:[1, 9, 3, 4]
        
        cout << "第一个元素:" << v.front() << endl; // 输出:1
        cout << "元素个数:" << v.size() << endl; // 输出:4
        
        // 转换为数组
        int* arr = v.data();
        cout << "数组第2个元素:" << arr[1] << endl; // 输出:9
        
        // 遍历
        for (int num : v) {
            cout << num << " "; // 输出:1 9 3 4
        }
        return 0;
    }
    
    适用场景:需要频繁按下标访问、尾部增删的场景(如成绩表、动态数组)。

    2. deque(双端队列)

    像 “两头都能进出的队伍”。可以在最前面或最后面快速加 / 删元素。
    ✅ 优点:两端操作效率高,比 vector 更适合频繁在开头加数据的场景。
    比如:实现一个队列,需要频繁从队头取元素、队尾加元素。

    核心特点
    • 内存分段连续(由多个连续内存块组成);
    • 支持下标访问(效率略低于 vector);
    • 头部和尾部插入 / 删除(push_front/pop_front)均高效(O (1));
    • 中间插入 / 删除低效(O (n))。
    专属语法(与 vector 的差异)
    #include <deque>
    #include <iostream>
    using namespace std;
    
    int main() {
        deque<string> dq;
        
        // 头部操作(vector不支持)
        dq.push_front("A"); // 头部添加:["A"]
        dq.push_front("B"); // 头部添加:["B", "A"]
        dq.pop_front(); // 头部删除:["A"]
        
        // 其他操作与vector类似
        dq.push_back("C"); // 尾部添加:["A", "C"]
        dq[1] = "D"; // 修改下标1的元素:["A", "D"]
        
        // 遍历
        for (const string& s : dq) {
            cout << s << " "; // 输出:A D
        }
        
        // 转换为数组(因内存分段,需先复制到vector)
        vector<string> temp(dq.begin(), dq.end());
        string* arr = temp.data(); // 数组:["A", "D"]
        return 0;
    }
    
    适用场景:需要在两端频繁插入 / 删除的场景(如双端队列、缓冲区)。

    3. list(双向链表)

    相当于 “一串用绳子连起来的珠子”。每个元素都带着前后元素的 “地址”,元素本身不连续存放。
    ✅ 优点:在中间任意位置插入 / 删除元素特别快(只需解开绳子重新连),不用挪动其他元素。
    ❌ 缺点:不能通过下标访问,必须从头一个个找,读数据效率低。
    比如:存一个频繁需要插入、删除中间元素的列表(如日程表中频繁调整事项)。

    核心特点
    • 内存不连续,元素通过前后指针连接;
    • 不支持下标访问;
    • 任意位置插入 / 删除(包括中间、头部、尾部)均高效(O (1));
    • 遍历效率低于 vector(因内存不连续,缓存命中率低)。
    专属语法与操作
    操作函数 / 语法示例代码说明
    头部添加 / 删除push_front()/pop_front()l.push_front(0);在头部插入 / 删除元素
    尾部添加 / 删除push_back()/pop_back()l.pop_back();在尾部插入 / 删除元素
    任意位置插入insert(迭代器, 值)l.insert(it, 2);在迭代器指向的位置插入元素
    任意位置删除erase(迭代器)l.erase(it);删除迭代器指向的元素
    反转链表reverse()l.reverse();反转整个链表的元素顺序
    排序sort()l.sort();对链表元素按升序排序(默认)
    完整示例
    #include <list>
    #include <vector> // 用于转换为数组
    #include <iostream>
    using namespace std;
    
    int main() {
        list<int> l = {1, 3, 5}; // 初始化:1 → 3 → 5
        
        // 中间插入(比vector高效)
        auto it = l.begin();
        ++it; // 指向3
        l.insert(it, 2); // 插入2:1 → 2 → 3 → 5
        
        // 头部添加
        l.push_front(0); // 0 → 1 → 2 → 3 → 5
        
        // 反转与排序
        l.reverse(); // 5 → 3 → 2 → 1 → 0
        l.sort(); // 0 → 1 → 2 → 3 → 5
        
        // 遍历
        cout << "链表元素:";
        for (int num : l) {
            cout << num << " "; // 输出:0 1 2 3 5
        }
        
        // 转换为数组(需复制到vector)
        vector<int> temp(l.begin(), l.end());
        int* arr = temp.data(); // 数组:[0, 1, 2, 3, 5]
        return 0;
    }
    
    适用场景:需要频繁在中间插入 / 删除元素的场景(如日程表、动态列表)。

    4. forward_list(单向链表)

    比 list 更简单,像 “只能往前串的珠子”,每个元素只知道下一个是谁,不知道上一个。
    ✅ 优点:更省内存,适合只需要从前往后遍历、且频繁改中间元素的场景。

    核心特点
    • 内存不连续,元素仅通过 “下一个指针” 连接,只能单向遍历;
    • 比 list 更节省内存(无需存储 “前向指针”);
    • 功能有限(无reversesize()等函数);
    • 头部插入 / 删除高效,中间插入 / 删除需通过insert_after/erase_after操作。
    专属语法与操作
    操作函数 / 语法示例代码说明
    头部添加 / 删除push_front()/pop_front()fl.push_front(1);在头部插入 / 删除元素
    指定位置后插入insert_after(迭代器, 值)fl.insert_after(it, 2);在迭代器指向元素的后面插入值
    指定位置后删除erase_after(迭代器)fl.erase_after(it);删除迭代器指向元素后面的元素
    排序sort()fl.sort();按升序排序(默认)
    完整示例
    #include <forward_list>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main() {
        forward_list<int> fl = {3, 1, 5}; // 单向链表:3 → 1 → 5
        
        // 头部添加
        fl.push_front(0); // 0 → 3 → 1 → 5
        
        // 在指定元素后插入(需先定位到目标元素)
        auto it = fl.begin(); // 指向0
        ++it; // 指向3
        fl.insert_after(it, 2); // 在3后面插入2:0 → 3 → 2 → 1 → 5
        
        // 删除元素(删除2后面的1)
        it = fl.begin(); // 重新定位:指向0
        ++it; // 指向3
        ++it; // 指向2
        fl.erase_after(it); // 删除2后面的元素(1):0 → 3 → 2 → 5
        
        // 排序
        fl.sort(); // 0 → 2 → 3 → 5
        
        // 遍历(只能单向)
        cout << "单向链表元素:";
        for (int num : fl) {
            cout << num << " "; // 输出:0 2 3 5
        }
        
        // 转换为数组(复制到vector)
        vector<int> temp;
        for (int num : fl) {
            temp.push_back(num);
        }
        int* arr = temp.data(); // 数组:[0, 2, 3, 5]
        return 0;
    }
    

    适用场景:内存受限,且只需单向遍历的场景(如简单的流程步骤记录、轻量级数据链)。
     

    顺序容器对比表

    容器类型下标访问尾部增删效率头部增删效率中间增删效率内存连续性转数组方式
    vector支持(快)O(1)O(n)O(n)连续直接用data()&v[0]获取数组首地址
    deque支持(较快)O(1)O(1)O(n)分段连续复制到 vector 后,用data()转数组
    list不支持O(1)O(1)O(1)不连续复制到 vector 后,用data()转数组
    forward_list不支持O(1)O(1)O(1)不连续复制到 vector 后,用data()转数组

    五、关联容器详解(像 “带标签的盒子”,数据按 “关键词” 排序或查找)

    关联容器通过 “键” 组织元素,支持快速查找,适合 “通过关键词定位数据” 的场景。

    特点:元素不是按插入顺序排,而是按某个 “关键词”(比如数值大小、字符串内容)自动排序或组织,方便快速查找。

    1. 有序关联容器(set/map 系列)

    基于红黑树实现,元素自动按 “键” 排序(默认升序),查找、插入、删除效率均为 O (log n)。

    (1)set(集合)

    相当于 “自动去重且排好序的抽屉”。里面的元素不重复,且会自动按从小到大(默认)排好。
    ✅ 优点:查找某个元素是否存在特别快,还能自动去重。
    比如:存一个班级的 unique 学号(不重复),想快速查某个学号是否存在。

    • 存储唯一元素(自动去重),元素本身即 “键”,按值升序排列。
    #include <set>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main() {
        set<int> s = {3, 1, 2, 2}; // 自动去重+排序:{1, 2, 3}
        
        s.insert(4); // 插入元素4:{1, 2, 3, 4}
        auto it = s.find(2); // 查找元素2(返回迭代器)
        if (it != s.end()) {
            cout << "找到元素:" << *it << endl; // 输出:找到元素:2
        }
        
        s.erase(3); // 删除元素3:{1, 2, 4}
        
        // 遍历(按升序)
        cout << "set元素:";
        for (int num : s) {
            cout << num << " "; // 输出:1 2 4
        }
        
        // 转换为数组(复制到vector)
        vector<int> temp(s.begin(), s.end());
        int* arr = temp.data(); // 数组:[1, 2, 4]
        return 0;
    }
    
    (2)map(映射)

    像 “带目录的笔记本”,每个元素是 “键 - 值对”(比如 “名字 - 成绩”),按键自动排序,通过 “键” 能快速找到 “值”。
    ✅ 优点:通过关键词(键)查数据特别快,比如用名字查成绩、用 ID 查用户信息。
    比如:存学生的 “姓名 - 电话” 对照表,想找 “张三” 的电话,直接用名字查。

    • 存储键值对key-value),键唯一且按升序排列,通过键快速访问值。
    #include <map>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main() {
        // 键:姓名(string),值:年龄(int)
        map<string, int> person = {{"张三", 18}, {"李四", 19}};
        
        person["张三"] = 19; // 修改键"张三"对应的值为19
        person["王五"] = 20; // 新增键值对{"王五", 20}
        
        // 查找键"李四"
        auto it = person.find("李四");
        if (it != person.end()) {
            cout << it->first << "的年龄:" << it->second << endl; // 输出:李四的年龄:19
        }
        
        // 遍历(按键升序)
        cout << "map元素:";
        for (auto& pair : person) {
            cout << pair.first << ":" << pair.second << " "; 
            // 输出:李四:19 王五:20 张三:19(按姓名首字母排序)
        }
        
        // 转换为数组(提取键或值到vector)
        vector<string> names; // 存储键(姓名)
        vector<int> ages;     // 存储值(年龄)
        for (auto& pair : person) {
            names.push_back(pair.first);
            ages.push_back(pair.second);
        }
        string* nameArr = names.data(); // 姓名数组:["李四", "王五", "张三"]
        int* ageArr = ages.data();     // 年龄数组:[19, 20, 19]
        return 0;
    }
    
    (3)multiset/multimap(允许重复)

    是 set 和 map 的 “允许重复版”。multiset 可以有重复元素,multimap 可以有相同的键。
    比如:multiset 存多个重复的分数(如统计成绩分布),multimap 存 “部门 - 员工”(一个部门可以有多个员工)。

    • multiset:允许元素重复(如统计多个相同分数),按值升序排列;
    • multimap:允许键重复(如 “部门 - 员工” 的一对多关系),按键升序排列。
    #include <multimap>
    #include <iostream>
    using namespace std;
    
    int main() {
        // 键:部门,值:员工(允许一个部门有多名员工)
        multimap<string, string> dept;
        dept.insert({"研发部", "张三"});
        dept.insert({"研发部", "李四"});
        dept.insert({"市场部", "王五"});
        
        // 查找研发部所有员工
        auto range = dept.equal_range("研发部"); // 返回键为"研发部"的元素范围
        cout << "研发部员工:";
        for (auto it = range.first; it != range.second; ++it) {
            cout << it->second << " "; // 输出:张三 李四
        }
        return 0;
    }
    

    2. 无序关联容器(unordered_set/unordered_map)(不排序,基于哈希)

    基于哈希表实现,元素不排序,查找、插入、删除效率更高(平均 O (1)),但不支持按顺序遍历。

    • 形象理解:像 “哈希表” 形式的收纳盒,数据不排序,但查起来比有序容器更快(平均时间更短)。
       
    • 核心特点
      • 基于哈希表实现,元素无序,查找、增删效率极高(平均 O (1))。
      • 缺点是不支持按顺序遍历(因为无序)。
    • 适用场景:只需要快速操作,不关心顺序(如存大量数据的快速查询)。
    #include <unordered_map>
    #include <iostream>
    using namespace std;
    
    int main() {
        // 键:学号,值:姓名(无序存储)
        unordered_map<int, string> idToName;
        idToName[1001] = "张三";
        idToName[1002] = "李四";
        idToName[1003] = "王五";
        
        // 快速查找
        cout << "学号1002对应的姓名:" << idToName[1002] << endl; // 输出:李四
        
        // 遍历(顺序不确定)
        cout << "无序map元素:";
        for (auto& pair : idToName) {
            cout << pair.first << ":" << pair.second << " "; 
            // 输出示例:1003:王五 1001:张三 1002:李四(顺序不固定)
        }
        return 0;
    }
    

    关联容器对比表

    容器类型元素类型键是否唯一是否排序查找效率适用场景
    set单一元素O(log n)去重 + 有序遍历(如唯一标签集合)
    map键值对O(log n)键值映射 + 有序遍历(如字典)
    multiset单一元素O(log n)允许重复 + 有序遍历(如成绩统计)
    multimap键值对O(log n)一对多映射 + 有序遍历(如部门 - 员工)
    unordered_set单一元素O(1)去重 + 快速查找(不关心顺序)
    unordered_map键值对O(1)键值映射 + 快速查找(不关心顺序)

    六、容器适配器详解(“改造过的盒子”,限制了操作方式)

    容器适配器是对现有容器的封装,接口更简洁,专为特定场景设计。

    特点:基于上面的顺序容器改造而来,只提供特定的操作方式,更符合某些场景的逻辑。

    1. stack(栈)

    像 “叠盘子”,只能从最上面放(压栈)和取(弹栈),“先进后出”。
    比如:实现网页的 “后退” 功能,每打开新页面就压栈,后退就弹栈

    • 逻辑:先进后出(LIFO,Last In First Out),如同叠放的盘子,只能操作顶端元素。
    核心操作
    操作函数 / 语法说明
    入栈push(值)在栈顶添加元素
    出栈pop()删除栈顶元素(无返回值,需先判断非空)
    访问栈顶top()获取栈顶元素的值(需先判断非空)
    判断空栈empty()栈为空时返回 true
    元素个数size()返回栈中元素的数量
    完整示例
    #include <stack>
    #include <iostream>
    using namespace std;
    
    int main() {
        stack<int> s;
        
        // 入栈
        s.push(10);
        s.push(20);
        s.push(30); // 栈:[10, 20, 30](30在顶)
        
        cout << "栈顶元素:" << s.top() << endl; // 输出:30
        cout << "栈中元素个数:" << s.size() << endl; // 输出:3
        
        // 出栈
        s.pop(); // 移除栈顶元素30 → 栈:[10, 20]
        cout << "出栈后栈顶:" << s.top() << endl; // 输出:20
        
        // 转换为数组(需弹出所有元素,顺序与入栈相反)
        vector<int> temp;
        while (!s.empty()) {
            temp.push_back(s.top());
            s.pop();
        }
        int* arr = temp.data(); // 数组:[20, 10]
        return 0;
    }
    
    适用场景:括号匹配、函数调用栈、深度优先搜索(DFS)。

    2. queue(队列)

    像 “排队买票”,只能从队尾加人,队头出人,“先进先出”。
    比如:处理打印任务,按顺序一个个来。

    • 逻辑:先进先出(FIFO,First In First Out),如同排队买票,先到先处理。
    核心操作
    操作函数 / 语法说明
    入队push(值)在队尾添加元素
    出队pop()删除队头元素(无返回值,需先判断非空)
    访问队头front()获取队头元素的值(需先判断非空)
    访问队尾back()获取队尾元素的值(需先判断非空)
    判断空队列empty()队列为空时返回 true
    元素个数size()返回队列中元素的数量
    完整示例
    #include <queue>
    #include <iostream>
    using namespace std;
    
    int main() {
        queue<string> q;
        
        // 入队
        q.push("任务1");
        q.push("任务2");
        q.push("任务3"); // 队列:[任务1, 任务2, 任务3](任务1在头)
        
        cout << "队头任务:" << q.front() << endl; // 输出:任务1
        cout << "队尾任务:" << q.back() << endl;   // 输出:任务3
        cout << "任务总数:" << q.size() << endl;   // 输出:3
        
        // 出队
        q.pop(); // 移除队头任务1 → 队列:[任务2, 任务3]
        cout << "出队后队头:" << q.front() << endl; // 输出:任务2
        
        // 转换为数组(弹出所有元素,顺序与入队一致)
        vector<string> temp;
        while (!q.empty()) {
            temp.push_back(q.front());
            q.pop();
        }
        string* arr = temp.data(); // 数组:[任务2, 任务3]
        return 0;
    }
    
    适用场景:任务调度、消息队列、广度优先搜索(BFS)。

    3. priority_queue(优先队列)

    像 “按优先级排队”,每次取出的都是当前 “最大” 或 “最重要” 的元素(比如数值最大的)。内部是 “堆” 结构,每次top取的是优先级最高的元素(默认最大)。
    比如:医院急诊,病情越重(优先级越高)的病人先处理。

    • 逻辑:每次出队的是 “优先级最高” 的元素(默认最大元素优先,可自定义优先级)。
    核心操作
    操作函数 / 语法说明
    入队push(值)添加元素,自动按优先级排序
    出队pop()删除优先级最高的元素(需先判断非空)
    访问队头top()获取优先级最高的元素(需先判断非空)
    判断空队列empty()队列为空时返回 true
    元素个数size()返回队列中元素的数量
    完整示例(默认最大优先)
    #include <queue>
    #include <iostream>
    using namespace std;
    
    int main() {
        priority_queue<int> pq; // 默认:值越大,优先级越高
        
        // 入队
        pq.push(3);
        pq.push(1);
        pq.push(5); // 内部按优先级排序:[5, 3, 1](5在顶)
        
        cout << "最高优先级元素:" << pq.top() << endl; // 输出:5
        
        // 出队
        pq.pop(); // 移除5 → 队列:[3, 1]
        cout << "出队后最高优先级:" << pq.top() << endl; // 输出:3
        
        // 转换为数组(弹出元素,顺序为优先级从高到低)
        vector<int> temp;
        while (!pq.empty()) {
            temp.push_back(pq.top());
            pq.pop();
        }
        int* arr = temp.data(); // 数组:[3, 1]
        return 0;
    }
    
    自定义优先级(最小优先)
    #include <queue>
    #include <vector> // 用于指定底层容器
    #include <iostream>
    using namespace std;
    
    int main() {
        // 定义最小值优先的优先队列(需指定底层容器和比较器)
        priority_queue<int, vector<int>, greater<int>> minPq;
        
        minPq.push(3);
        minPq.push(1);
        minPq.push(5); // 内部排序:[1, 3, 5](1在顶)
        
        cout << "最高优先级元素(最小):" << minPq.top() << endl; // 输出:1
        return 0;
    }
    
    适用场景:急诊调度(病情重者优先)、任务优先级排序、Top K 问题。
     

      七、容器选择指南(新手必看)

      选择容器的核心原则是 “按需选择”,根据数据操作的核心需求匹配容器特性:

      核心需求场景推荐容器选择理由
      按下标访问、尾部增删频繁vector下标访问效率最高,尾部操作 O (1)
      两端增删频繁deque头部和尾部操作均为 O (1)
      中间增删频繁list无需挪动元素,增删效率 O (1)
      内存受限 + 单向遍历forward_list比 list 更省内存,适合单向访问
      去重 + 需要有序遍历set自动去重且按值排序
      键值映射 + 需要有序遍历map按键排序,支持顺序遍历
      去重 + 快速查找(不关心顺序)unordered_set哈希存储,查找效率 O (1)
      键值映射 + 快速查找(无序)unordered_map哈希存储,查找效率高于 map
      先进后出(如撤销操作)stack严格遵循 LIFO 逻辑
      先进先出(如排队场景)queue严格遵循 FIFO 逻辑
      按优先级处理元素priority_queue自动按优先级出队

      小结:怎么选容器?

      • 想按顺序存,频繁用下标访问?选 vector
      • 频繁在中间插删?选 list
      • 想快速查 “有没有某个元素” 或去重?选 set
      • 想通过关键词(如名字)查对应的值?选 map
      • 想模拟 “叠盘子”“排队”?选 stack 或 queue

      这些容器都是 C++ 标准库自带的,不用自己写复杂的逻辑,直接拿来用就行,省事儿又高效。
       

      八、总结

      C++ 容器是管理批量数据的 “利器”,不用自己写复杂的存储逻辑,直接调用接口即可。掌握它们的关键在于:

      1. 理解三大类容器的核心特点:顺序容器强调 “顺序”,关联容器强调 “查找”,适配器强调 “专用场景”;
      2. 熟悉通用语法:初始化、迭代器、通用成员函数是操作所有容器的基础;
      3. 掌握专属操作:不同容器有独特的成员函数(如 vector 的push_back、map 的find);
      4. 按需选择容器:根据数据操作特点(如访问方式、增删位置、是否需要排序)选择最合适的容器;
      5. 容器与数组转换:内存连续的容器(如 vector)可直接转数组,其他容器需通过 vector 中转。

      通过多动手练习(如用 vector 存成绩、用 map 做通讯录、用 stack 处理括号匹配),能快速掌握容器的使用技巧,为后续编程打下坚实基础。

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值