C++标准库大全(STL)

发布于:2025-06-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

C++标准库大全(STL)


1. 容器(Containers)
*问题类型:

  • 序列容器(std::vector, std::deque, std::list, std::forward_list, std::array, std::string):

    • 各自的特点、底层实现、优缺点和适用场景?

      容器 特点 底层实现 优点 缺点 适用场景
      std::vector 动态数组,支持快速随机访问 连续内存 + 三指针(数据头/尾/容量尾) 随机访问 O(1);缓存友好;尾部操作高效 中间插入/删除 O(n);扩容需数据拷贝 随机访问为主;尾部增删(如数据缓存)
      std::deque 双端队列,头尾插入高效 分段连续数组 + 中央映射表 头尾插入/删除 O(1);支持随机访问 O(1) 中间插入 O(n);内存碎片化;访问速度略慢于 vector 队列/栈需双端操作(如任务调度)
      std::list 双向链表,任意位置插入高效 双向链表节点(prev/data/next) 任意位置插入/删除 O(1);无扩容开销 随机访问 O(n);内存占用高;缓存不友好 频繁中间增删(如 LRU 缓存)
      std::forward_list 单向链表,内存更省 单向链表节点(data/next) 内存开销极低(单指针);插入/删除 O(1) 仅单向遍历;无反向迭代器;操作需前驱节点 内存敏感场景(如嵌入式系统链表)
      std::array 固定大小数组,编译时确定尺寸 栈上分配的 C 风格数组封装 零内存开销;随机访问 O(1);无动态分配成本 大小不可变;无动态扩容 替代 C 数组(如固定尺寸矩阵运算)
      std::string 动态字符串,支持丰富操作 类 vector + SSO 优化 自动内存管理;内置操作(find/replace 等) 性能略低于 char*;SSO 有大小限制 字符串处理(如文本解析/日志拼接)
      1. vector 扩容

        • 扩容因子(1.5/2倍)平衡 均摊 O(1) 时间成本内存碎片问题
          • 1.5 倍:释放的内存块可被后续扩容重用(例:释放 4MB 后,1.5 倍扩容 6MB 可重用该内存)。
          • 2 倍:内存重用困难(例:4MB → 8MB → 16MB,释放的 4MB+8MB 无法用于 16MB)。
      2. deque 随机访问

        • 计算过程:元素位置 = 段起始地址 + 段内偏移,比 vector 多一次地址跳转。
      3. SSO(短字符串优化)

        • string 对短字符串(通常 ≤15 字符)直接在栈存储,避免堆分配:

          std::string s = "Short";  // 栈存储  
          std::string l = "Long string over 15 chars";  // 堆存储  
          
      4. 链表选择指南

        • 需双向遍历 → list
        • 仅需单向遍历 + 省内存 → forward_list
        • 高频中间插入 → 优先链表;高频随机访问 → 优先 vector/deque
      5. 性能临界场景

        • 超高频操作(如金融交易):优先 array/vector(缓存友好)
        • 内存敏感(如嵌入式):优先 forward_list/array
    • vector的扩容机制?为什么是 2 倍或 1.5 倍?

      • 扩容机制

        • 当插入元素超过当前容量时,分配新内存(原容量的 n 倍),拷贝旧数据到新空间,释放旧内存。
      • 扩容因子(1.5或2倍)的原因

        1. 均摊时间复杂度

          • 扩容因子需保证插入操作的均摊时间复杂度为 O(1)。数学证明:当因子 k > 1 时,均摊成本为 O(1)
        2. 内存重用

          • 1.5倍:旧内存块释放后,后续扩容可能重用(新旧内存块大小无重叠)。

            例如:释放 size=M 后,新申请 1.5M,后续扩容可能使用之前释放的 M 内存。

          • 2倍:释放的内存块总和(M + 2M + 4M + ...)无法被后续更大的块重用(如 8M)。

        3. 折中策略

          • 过小(如1.1倍):扩容频繁,拷贝开销大。
          • 过大(如3倍):内存浪费严重。
        • 主流实现
          • GCC:2倍扩容;
          • VS:1.5倍扩容。
    • vectorlist的区别?

      特性 vector list
      底层结构 动态数组(连续内存) 双向链表(非连续内存)
      随机访问 O(1)(支持下标访问) O(n)(需遍历)
      头部插入/删除 O(n)(移动元素) O(1)
      尾部插入/删除 均摊 O(1) O(1)
      中间插入/删除 O(n)(移动元素) O(1)(定位后操作)
      内存占用 较少(仅需存储数据) 较高(每个节点含两个指针)
      缓存友好性 高(连续内存) 低(内存碎片化)
      扩容开销 需重新分配内存和拷贝 无扩容(动态分配节点)
    • stringchar*的区别?

      特性 string char*
      内存管理 自动分配/释放(RAII) 手动管理(malloc/free
      安全性 防越界(自动扩容) 易缓冲区溢出/内存泄漏
      功能扩展 丰富成员函数(findappend等) 依赖C库函数(strcpystrcat
      结尾标识 可包含 \0(长度独立管理) \0 结尾
      性能开销 略高(封装成本) 极低(直接操作内存)
      兼容性 C++专用 兼容C/C++

      关键结论

      • 优先使用 string:安全、便捷,适合大多数场景。
      • 使用 char*:需兼容C或极限性能优化(如高频交易系统)。
  • 关联容器(std::set, std::multiset, , std::map, std::multimap):

    • 各自的特点、底层实现(红黑树?)、优缺点和适用场景

      容器 特点 底层实现 优点 缺点 适用场景
      std::set 唯一键集合,自动排序 红黑树 有序遍历;查找/插入 O(log n) 内存占用高(每个节点额外信息) 需有序唯一键(如字典)
      std::multiset 键可重复,自动排序 红黑树 支持重复键;有序 set 需有序但键可重复(如成绩排名)
      std::map 键值对(键唯一),按键排序 红黑树 按键有序;范围查询高效 set 键值映射需有序(如学生ID→信息)
      std::multimap 键值对(键可重复),按键排序 红黑树 支持重复键;有序 set 一键多值(如作者→著作列表)
      底层实现核心:红黑树
      • 特性
        • 自平衡二叉搜索树(保证树高 ≈ log n)。
        • 节点含颜色标记(红/黑),通过旋转和变色维持平衡。
      • 操作复杂度
        • 查找、插入、删除:O(log n)
      • 额外开销
        • 每个节点存储父/子指针、颜色标记(内存占用高于哈希表)。
    • mapunordered_map的区别?

      特性 std::map std::unordered_map
      底层实现 红黑树(平衡二叉搜索树) 哈希表(桶数组 + 链表/红黑树)
      元素顺序 按键有序(升序) 无序(取决于哈希函数)
      查找复杂度 O(log n) 平均 O(1),最坏 O(n)
      内存占用 较低(树结构) 较高(预分配桶 + 链表指针)
      键类型要求 需支持 < 或自定义比较器 需支持哈希函数和 == 比较
      适用场景 需有序访问/范围查询 只需快速查找(如缓存/计数器)
      关键区别:
      1. 顺序需求
        • map:保证顺序(遍历时按键升序)。
        • unordered_map:元素顺序不可控(依赖哈希函数)。
      2. 性能权衡
        • unordered_map:平均 O(1) 查找,但哈希冲突时退化(最坏 O(n))。
        • map:稳定 O(log n),无性能波动。
      3. 内存敏感场景
        • 优先 map(无预分配桶开销)。
      4. C++11 优化
        • unordered_map 桶内改用红黑树(如 GCC),最坏复杂度优化至 O(log n)
  • 如何自定义容器的比较函数(std::less)?

    1. 关联容器(set/map等)

    在模板参数中指定自定义比较类型:

    // 方法1:函数对象(推荐)  
    struct CustomCompare {  
        bool operator()(const T& a, const T& b) const {  
            return /* 自定义逻辑 */;  // 例:a.salary > b.salary  
        }  
    };  
    std::set<T, CustomCompare> mySet;  
    
    // 方法2:Lambda(C++11+)  
    auto comp = [](const T& a, const T& b) { /* ... */ };  
    std::set<T, decltype(comp)> mySet(comp);  // 需传递Lambda对象  
    
    1. 序列容器(vector/list等)

    在排序操作时传入比较函数:

    std::vector<T> vec;  
    // 方法1:Lambda表达式  
    std::sort(vec.begin(), vec.end(), [](const T& a, const T& b) {  
        return a.id < b.id;  // 按ID升序  
    });  
    
    // 方法2:函数指针  
    bool compareFunc(const T& a, const T& b) { /* ... */ }  
    std::sort(vec.begin(), vec.end(), compareFunc);  
    
    1. 优先队列(priority_queue

    在模板参数中指定比较类型:

    // 小顶堆示例  
    struct Greater {  
        bool operator()(int a, int b) const {  
            return a > b;  // 小值优先  
        }  
    };  
    std::priority_queue<int, std::vector<int>, Greater> minHeap;  
    

    关键规则

    1. 严格弱序要求
      • 需满足:

        !comp(a, a)               // 非自反  
        comp(a, b) => !comp(b, a)  // 非对称  
        comp(a, b) && comp(b, c) => comp(a, c)  // 传递性  
        
      • 违反示例(错误):

        [](int a, int b) { return a <= b; }  // 包含相等,违反非自反性  
        
    2. 比较对象类型
      • 函数对象:需重载 operator() 且为 const 成员函数
      • Lambda:推荐捕获列表为空(无状态)
    3. 性能影响
      • 关联容器:比较函数复杂度需为 O(1),否则树操作退化为 O(n log n)
      • 排序操作:比较函数应轻量(高频调用)
  • 无序关联容器(std::unordered_set, std::unordered_multiset, std::unordered_map, std::unordered_multimap):

    • 各自的特点、底层实现(哈希表)、优缺点和适用场景?

      容器 特点 底层实现 优点 缺点 适用场景
      std::unordered_set 唯一键集合,无序存储 哈希表(桶+链表/红黑树) 平均 O(1) 查找/插入 最坏 O(n);内存占用高 快速去重(如URL黑名单)
      std::unordered_multiset 键可重复,无序存储 同上 支持重复键 unordered_set 词频统计(如单词计数)
      std::unordered_map 键值对(键唯一),无序存储 同上 平均 O(1) 键值访问 unordered_set 高速键值查询(如缓存)
      std::unordered_multimap 键值对(键可重复),无序存储 同上 支持一键多值 unordered_set 多值映射(如电话簿)

      底层实现核心

      • 哈希表 = 桶数组(连续内存) + 冲突解决结构(链表/红黑树)
      • C++11 优化:桶内元素超阈值(如 GCC 为 8)时,链表转红黑树(最坏复杂度优化至 O(log n)
    • 哈希冲突的解决方法?

      方法 原理 实现示例 特点
      链地址法 桶内挂链表存储冲突元素 bucket[i] = head→a→b→null 简单通用;C++标准库默认方案
      开放寻址法 线性探测:冲突时找下一个空桶 bucket[(hash+1)%size] 缓存友好;需负载因子控制
      罗宾汉哈希 冲突时比较探测距离,抢占更远槽位 复杂优化 减少最长探测距离

      关键参数

      • 负载因子 = 元素数 / 桶数(默认 1.0)
      • 扩容触发:负载因子 > max_load_factor() 时,桶数翻倍并重哈希
    • 如何自定义容器的比较函数?

      需定义 两个函数对象

      1. 哈希函数(计算键的哈希值)
      2. 键相等比较(判断键是否相同)
      // 示例:自定义Point类型作为键  
      struct Point { int x; int y; };  
      
      // 1. 自定义哈希函数  
      struct PointHash {  
          size_t operator()(const Point& p) const {  
              return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);  
          }  
      };  
      
      // 2. 自定义键相等比较  
      struct PointEqual {  
          bool operator()(const Point& a, const Point& b) const {  
              return a.x == b.x && a.y == b.y;  
          }  
      };  
      
      // 定义容器  
      std::unordered_map<Point, std::string, PointHash, PointEqual> pointMap;  
      

      关键规则

      • 哈希一致性:若 a == b,则 hash(a) == hash(b)

      • 性能要求:哈希函数需高效(O(1) 复杂度)

      • 特化 std::hash(可选):

        namespace std {  
        template<> struct hash<Point> { /* ... */ };  
        }    
        
  • 容器配合(std::stack, std::queue, , std::priority_queue):

    • 各自的特点、底层实现、优缺点和适用场景?

      适配器 特点 默认底层容器 核心操作 优点 缺点 适用场景
      std::stack 后进先出(LIFO) std::deque push(), pop(), top() 操作简单高效(O(1)) 只能访问顶部元素 函数调用栈/撤销操作/括号匹配
      std::queue 先进先出(FIFO) std::deque push(), pop(), front() 头尾操作高效(O(1)) 只能访问两端元素 消息队列/缓冲区/广度优先搜索
      std::priority_queue 优先级队列(最大堆) std::vector push(), pop(), top() 高效获取极值(O(1)) 插入慢(O(log n)) 任务调度/事件处理/Dijkstra算法

      底层实现详解

      1. stack & queue 默认容器选择
        • 使用 deque(双端队列)原因:

          • 两端插入/删除均为 O(1)

          • 内存自动扩展(避免 vector 扩容时的全量拷贝)

          • 示例:

            std::stack<int> s;       // 等价于 std::stack<int, std::deque<int>>  
            std::queue<char> q;      // 等价于 std::queue<char, std::deque<char>>  
            
      2. priority_queue 实现原理
        • 基于 堆结构(完全二叉树)

        • 底层容器需支持:

          • 随机访问(operator[])→ 故用 vector
          • 前端插入/删除(push_back(), pop_back()
        • 堆操作:

          // 插入元素(上浮)
          vec.push_back(x); 
          std::push_heap(vec.begin(), vec.end());
          
          // 删除顶部(下沉)
          std::pop_heap(vec.begin(), vec.end());
          vec.pop_back();
          

      自定义底层容器

      // 使用 vector 作为 stack 的底层容器
      std::stack<int, std::vector<int>> vecStack;  
      
      // 使用 list 作为 queue 的底层容器
      std::queue<int, std::list<int>> listQueue;  
      
      // 使用 deque 作为 priority_queue 的底层容器
      std::priority_queue<int, std::deque<int>> dequePQ;  
      

      注意限制

      • stack:需支持 push_back(), pop_back(), back()
      • queue:需支持 push_back(), pop_front(), front()
      • priority_queue:需支持随机访问迭代器 + front()

      性能与选择指南

      1. stack/queue vs 原生容器
        • 优先用适配器:语义明确 + 防止误操作
        • 需要中间访问时 → 改用 deque
      2. priority_queue 优化
        • 自定义比较器实现最小堆:

          std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
          
        • 复杂类型场景:

          struct Task { int priority; /*...*/ };  
          auto comp = [](const Task& a, const Task& b) { 
              return a.priority < b.priority; 
          };  
          std::priority_queue<Task, std::vector<Task>, decltype(comp)> taskQueue(comp);
          
      3. 内存敏感场景
        • 固定大小栈 → 用 array 替代 deque

          std::stack<int, std::array<int, 100>> fixedStack;
          

2. 修改算法(算法)
*问题类型:

  • 常用的非式修改序列操作(std::for_each, std::find,std::count等)?

    算法 功能 示例
    std::for_each 对每个元素执行操作 for_each(v.begin(), v.end(), print)
    std::find 查找首个匹配元素 find(v.begin(), v.end(), 42)
    std::find_if 查找首个满足条件的元素 find_if(v.begin(), v.end(), isEven)
    std::count 统计匹配元素数量 count(v.begin(), v.end(), 'a')
    std::count_if 统计满足条件的元素数量 count_if(v.begin(), v.end(), isPrime)
    std::all_of 检查所有元素满足条件 (C++11) all_of(v.begin(), v.end(), isPositive)
    std::any_of 检查至少一个元素满足条件 (C++11) any_of(v.begin(), v.end(), isNegative)
  • 常用的非式序列操作(std::copy, std::remove, std::sort, std::unique,std::reverse等)?

    算法 功能 注意要点
    std::copy 复制序列到目标位置 目标容器需预分配空间
    std::remove 逻辑删除匹配元素(移动元素) 需配合 erase 物理删除(见示例↓)
    std::remove_if 逻辑删除满足条件的元素 同上
    std::sort 排序(默认升序) 需随机访问迭代器(不支持 list
    std::stable_sort 稳定排序(保持相等元素顺序) 性能略低于 sort
    std::unique 删除连续重复元素 需先排序;返回新逻辑终点
    std::reverse 反转序列元素顺序 list 有成员函数 reverse()

    删除元素标准写法

    // vector 删除偶数  
    auto newEnd = remove_if(vec.begin(), vec.end(), isEven);  
    vec.erase(newEnd, vec.end());  // 物理删除  
    
  • 常用的数值算法(std::accumulate等)?

    算法 功能 示例
    std::accumulate 累加/自定义归约操作 accumulate(v.begin(), v.end(), 0)
    std::inner_product 计算内积(点积) inner_product(a.begin(), a.end(), b.begin(), 0)
    std::partial_sum 生成前缀和序列 partial_sum(v.begin(), v.end(), out)
    std::iota 填充递增序列 (C++11) iota(v.begin(), v.end(), 10) // 10,11,12…
  • 如何使用这些算法以及它们与容器成员函数的区别?

    场景 选择 原因
    list 排序 成员 sort() 算法 std::sort 需随机访问(不支持链表)
    set 查找 成员 find() 算法 std::find 是 O(n),成员是 O(log n)
    关联容器删除 成员 erase() 算法 std::remove 破坏树结构
    通用序列操作 STL 算法 统一接口,可跨容器使用

    关键原则

    • 关联容器/链表:优先用成员函数(性能/正确性)
    • 序列容器(vector/deque):优先 STL 算法(通用性)
  • std::sort底层实现?(通常是 Introsort)

    • 混合排序策略

      1. 快速排序:主递归阶段(平均 O(n log n))
      2. 堆排序:当递归深度 > 2 log n 时切换(避免最坏 O(n²))
      3. 插入排序:小区间优化(n ≤ 16 时)
    • 核心优势

      • 最坏时间复杂度 O(n log n)(优于纯快排)
      • 避免递归过深(堆排序兜底)
      • 小数据局部性优(插入排序)
    • 示例代码

      std::vector<int> v = {5, 3, 2, 8, 1};  
      std::sort(v.begin(), v.end());  // 升序  
      std::sort(v.begin(), v.end(), std::greater<int>());  // 降序  
      

3. 迭代器(Iterators)
*问题类型:

  • 迭代器的概念和?

    • 定义:迭代器是 STL 中用于 遍历容器元素 的通用抽象接口,行为类似指针(支持 *->++ 等操作)。
    • 作用
      • 解耦算法与容器(如 std::sort 可作用于所有支持随机访问迭代器的容器)
      • 提供统一的容器访问方式
  • 五种迭代器作用类别(输入、输出、前向、个体、随机访问)及其特性?

    类别 支持操作 容器示例
    输入迭代器 只读单次遍历 (*it, ++, ==, !=) istream_iterator
    输出迭代器 只写单次遍历 (*it=, ++) ostream_iterator
    前向迭代器 读写 + 多次遍历(继承输入/输出) forward_list, unordered_*
    双向迭代器 前向 + 反向遍历 (--) list, set, map
    随机访问迭代器 双向 + 跳跃访问 (it+n, it[n], <) vector, deque, array

    层级关系
    输入 → 前向 → 双向 → 随机访问
    输出 → 前向 → …

  • begin(), end(), cbegin(), cend(), rbegin(),rend()的区别?

    函数 返回类型 方向 可修改性 容器示例调用
    begin() 普通迭代器 正向 可读写 vec.begin()
    end() 普通迭代器 正向尾后 不可解引用 vec.end()
    cbegin() const 迭代器 正向 只读 vec.cbegin()
    cend() const 迭代器 正向尾后 不可解引用 vec.cend()
    rbegin() 反向迭代器 反向 可读写 vec.rbegin()
    rend() 反向迭代器 反向尾后 不可解引用 vec.rend()
    crbegin() const 反向迭代器 反向 只读 vec.crbegin()

    反向迭代器注意

    std::vector<int> v = {1,2,3};  
    auto rit = v.rbegin(); // 指向 3  
    *rit = 4;              // v = {1,2,4}   
    
  • 迭代器故障问题,何时会发生,如何避免?

    • 何时发生

      容器 导致失效的操作
      vector/string 插入/删除(导致扩容或元素移动)
      deque 首尾外插入/删除、扩容
      list/forward_list 仅删除时使被删元素迭代器失效
      关联容器 仅删除时使被删元素迭代器失效
    • 避免方法

      1. 插入后更新迭代器

        auto it = vec.begin();  
        it = vec.insert(it, 10); // 获取新迭代器  
        
      2. 删除时用返回值更新

        for (auto it = lst.begin(); it != lst.end(); ) {  
            if (*it % 2 == 0) it = lst.erase(it); // 更新为下一个元素  
            else ++it;  
        }  
        
      3. 避免失效区间操作

        // 错误:删除导致后续迭代器失效  
        for (auto it = vec.begin(); it != vec.end(); ++it) {  
            if (cond) vec.erase(it); // UB!  
        }  
        

    关键原则

    • 序列容器(vector/deque)修改后所有迭代器可能失效
    • 链表/关联容器修改后仅被删元素迭代器失效

4. 函数对象 (Functors) / Lambda 表达式
*问题类型:

  • 函数对象的概念和作用?

    • 概念:重载了 operator() 的类对象,可像函数一样调用

    • 作用

      • 可携带状态(通过成员变量)
      • 可作模板参数(编译期多态)
      • 比函数指针更高效(可内联优化)
    • 示例

      struct Compare {  
          bool operator()(int a, int b) const {  
              return a > b;  // 实现自定义比较  
          }  
      };  
      std::sort(v.begin(), v.end(), Compare());  
      
  • Lambda 表达式的语法和捕获列表(Capture List)?

    • 语法

      [捕获列表](参数) -> 返回类型 { 函数体 }  
      
    • 捕获列表(Capture List)

      捕获方式 效果
      [] 不捕获外部变量
      [x] 按值捕获变量 x(副本)
      [&x] 按引用捕获变量 x
      [=] 按值捕获所有外部变量(不推荐!)
      [&] 按引用捕获所有外部变量(不推荐!)
      [this] 捕获当前类对象的 this 指针
      [x = expr] C++14:用表达式初始化捕获(移动捕获)
    • 示例

      int base = 100;  
      auto add = [base](int x) { return x + base; };  
      std::cout << add(5);  // 输出 105  
      
  • Lambda 表达式的本质?

    • 编译器行为
      将 Lambda 转换为匿名函数对象类(含重载的 operator()

    • 转换示例

      // Lambda: [](int x) { return x * 2; }  
      // 编译器生成类似:  
      class __Lambda_123 {  
      public:  
          int operator()(int x) const { return x * 2; }  
      };  
      
  • 什么时候使用函数对象或 Lambda 表达式?

    场景 推荐方式 原因
    简单逻辑(如比较器) Lambda 代码简洁,无需额外定义
    需要携带复杂状态 函数对象 可定义多个成员变量/辅助方法
    需要递归调用 函数对象 Lambda 递归需 std::function(性能损失)
    需作为模板参数(如容器的比较器) 函数对象或无捕获 Lambda 无捕获 Lambda 可转换为函数指针
    需复用逻辑 函数对象 避免重复定义

    关键区别

    • 函数对象:显式定义类型,适合复杂/复用逻辑
    • Lambda:隐式匿名类型,适合一次性简单逻辑