news 2026/10/15 20:59:00

C++ STL set与map深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ STL set与map深度解析

好的,我们来全面解析 C++ 标准模板库 (STL) 中的set和map容器。它们都是关联容器,核心功能是基于键高效地存储和检索数据。

一、 共同基础:有序性与底层结构

  1. 有序性 (Sorted):

    • set和map中的元素(对于set是整个元素,对于map是键)在插入时会自动根据键进行排序。
    • 默认使用<运算符(std::less<Key>)进行升序排序。用户可以提供自定义的比较函数或函数对象来定义排序规则。
    • 这种有序性使得它们支持高效的范围查询(如查找某个范围内的所有元素)和顺序遍历。
  2. 底层数据结构:红黑树 (Red-Black Tree)

    • 在绝大多数 C++ 标准库实现中,set和map的底层都是基于红黑树实现的。
    • 红黑树是一种自平衡的二叉搜索树(Self-balancing Binary Search Tree)。
    • 特性:
      • 插入、删除、查找操作的时间复杂度平均和最坏情况均为 $O(\log n)$。
      • 树的高度始终保持在对数级别,保证了操作的效率。
      • 正是这种结构提供了自动排序和高效查找的能力。

二、 std::set

  1. 定义与特性:

    • std::set是一个关联容器,专门用于存储唯一的键(Key)。在set中,键就是值本身。
    • 每个元素在set中都是唯一的。尝试插入重复的元素会被忽略(插入操作返回一个pair<iterator, bool>,其中bool为false表示插入失败)。
    • 元素一旦插入,不能被修改(因为修改可能破坏排序)。要“修改”一个元素,通常的做法是先删除旧元素,再插入新元素。
    • 元素类型 (T) 必须支持比较操作(通常通过<运算符或自定义比较器)。
  2. 常用操作:

    • 插入:insert(const T& value),insert(T&& value),insert(iterator hint, const T& value)(带提示插入)。
    • 查找:find(const Key& key)(返回迭代器),count(const Key& key)(返回 0 或 1),contains(const Key& key)(C++20, 返回 bool)。
    • 删除:erase(iterator pos),erase(const Key& key),erase(iterator first, iterator last)。
    • 遍历:使用迭代器 (begin(),end(),cbegin(),cend(),rbegin(),rend())。遍历时元素按排序顺序输出。
    • 大小:size(),empty().
    • 范围查询:lower_bound(const Key& key),upper_bound(const Key& key),equal_range(const Key& key)(返回包含相等元素的迭代器范围)。
  3. 典型用例:

    • 存储需要自动排序且唯一的元素集合。
    • 检查一个元素是否存在于集合中(find,count,contains)。
    • 获取集合中最小或最大的元素 (begin(),rbegin())。
    • 需要频繁插入、删除、查找且元素唯一的场景。
  4. 示例:

#include <iostream> #include <set> #include <string> int main() { std::set<std::string> names; // 插入 names.insert("Alice"); names.insert("Bob"); names.insert("Alice"); // 重复插入,被忽略 auto [it, success] = names.insert("Charlie"); // C++17 结构化绑定 // 查找 if (names.find("Bob") != names.end()) { std::cout << "Found Bob!" << std::endl; } if (names.contains("David")) { // C++20 std::cout << "Found David!" << std::endl; // 不会执行 } // 遍历 (有序输出) for (const auto& name : names) { std::cout << name << " "; // 输出: Alice Bob Charlie } std::cout << std::endl; // 删除 names.erase("Bob"); // 大小 std::cout << "Number of names: " << names.size() << std::endl; // 输出: 2 return 0; }

三、 std::map

  1. 定义与特性:

    • std::map是一个关联容器,用于存储键值对 (Key-Value pairs)。每个元素是一个std::pair<const Key, Value>。
    • 键 (Key)在map中是唯一的。尝试插入具有相同键的键值对会被忽略(插入操作返回pair<iterator, bool>,bool为false)。
    • 键 (Key)一旦插入,不能被修改(因为修改可能破坏排序)。
    • 值 (Value)可以通过迭代器或operator[]进行修改。
    • 键类型 (Key) 必须支持比较操作。
  2. 常用操作:

    • 插入:insert(const std::pair<const Key, Value>& kv),insert(std::pair<const Key, Value>&& kv),emplace(...)(原地构造), 带提示插入。
    • 查找:find(const Key& key)(返回指向键值对的迭代器),count(const Key& key)(返回 0 或 1),contains(const Key& key)(C++20)。
    • 访问值:
      • operator[](const Key& key): 如果键存在,返回对应值的引用;如果键不存在,则插入一个具有该键和值初始化(Value()或零初始化)的新元素,并返回其值的引用。慎用,因为它可能无意中插入新元素。
      • at(const Key& key): 如果键存在,返回对应值的引用;如果键不存在,抛出std::out_of_range异常。更安全。
    • 删除:erase(iterator pos),erase(const Key& key),erase(iterator first, iterator last)。
    • 遍历:使用迭代器。遍历时按键的排序顺序输出键值对。
    • 大小:size(),empty().
    • 范围查询:lower_bound(const Key& key),upper_bound(const Key& key),equal_range(const Key& key)。
  3. 典型用例:

    • 实现字典或映射(如单词到其释义的映射)。
    • 存储需要通过唯一键快速访问的数据(如学生ID到学生信息的映射)。
    • 需要按键排序的键值对集合。
    • 需要频繁按键查找、插入、删除的场景。
  4. 示例:

#include <iostream> #include <map> #include <string> int main() { std::map<int, std::string> studentMap; // 插入方式1:insert + pair studentMap.insert(std::make_pair(101, "Alice")); studentMap.insert({102, "Bob"}); // C++11 列表初始化 // 插入方式2:emplace (原地构造pair) studentMap.emplace(103, "Charlie"); // 尝试插入重复键 auto [it, inserted] = studentMap.insert({101, "Someone Else"}); if (!inserted) { std::cout << "Insert failed for ID 101 (already exists)." << std::endl; } // 访问方式1: operator[] (注意行为:查找或插入) std::cout << "Student 102: " << studentMap[102] << std::endl; // 输出: Bob // 访问不存在的键,会插入新元素! std::cout << "Student 104: " << studentMap[104] << std::endl; // 输出空字符串,并插入了 {104, ""} std::cout << "Size after accessing 104: " << studentMap.size() << std::endl; // 输出: 4 // 访问方式2: at() (更安全) try { std::cout << "Student 105: " << studentMap.at(105) << std::endl; // 抛出 out_of_range } catch (const std::out_of_range& e) { std::cerr << "Error: " << e.what() << std::endl; } // 访问方式3: find (安全,推荐) auto itFind = studentMap.find(103); if (itFind != studentMap.end()) { std::cout << "Found Student 103: " << itFind->second << std::endl; // 输出: Charlie } // 修改值 (键不能改) studentMap[102] = "Robert"; // 修改 Bob 为 Robert // 遍历 (按键排序) for (const auto& [id, name] : studentMap) { // C++17 结构化绑定 std::cout << "ID: " << id << ", Name: " << name << std::endl; } // 删除 studentMap.erase(104); // 删除之前无意插入的 {104, ""} return 0; }

四、 set 与 map 的关键差异总结

特性std::setstd::map
存储内容唯一的键 (Key=Value)唯一的键与关联的值 (std::pair<const Key, Value>)
元素类型Keystd::pair<const Key, Value>
访问值无单独的值概念可通过迭代器或[]/at访问关联值Value
修改元素不能修改(需删除再插入)键不能修改,值可以修改
插入操作insert(Key)insert(pair)或emplace(Args...)
operator[]不支持支持 (查找或插入)

五、 变体:multiset 和 multimap

  • std::multiset和std::multimap允许存储重复的键。
  • 它们的接口与set和map非常相似,主要区别在于:
    • insert操作总是成功(返回指向新元素的迭代器)。
    • count可能返回大于 1 的值。
    • find返回指向第一个匹配元素的迭代器。
    • equal_range对于查找具有相同键的所有元素非常有用。

六、 性能与选择

  • 优势:
    • 有序性,支持范围查询和顺序遍历。
    • 插入、删除、查找操作均为 $O(\log n)$ 复杂度,对于需要频繁这些操作的中大型数据集效率高。
    • 基于树的结构使得迭代器在插入/删除后(除了被删除的元素)通常保持有效。
  • 劣势:
    • 相比顺序容器 (vector,list) 或无序关联容器 (unordered_set,unordered_map),内存开销稍大(树节点结构)。
    • 虽然 $O(\log n)$ 很快,但在只需要查找且对顺序无要求的场景下,unordered_set/map(哈希表实现) 的 $O(1)$ 平均复杂度更快。
  • 选择建议:
    • 需要元素唯一且有序->set。
    • 需要键唯一、键值对、按键有序->map。
    • 需要元素可重复且有序->multiset。
    • 需要键可重复、键值对、按键有序->multimap。
    • 只需要快速查找/插入/删除,不关心顺序-> 优先考虑unordered_set或unordered_map(哈希表)。

七、 总结

set和map是 C++ STL 中强大的、基于红黑树实现的有序关联容器。set专注于存储唯一的键集合,而map存储唯一的键及其关联的值。它们提供了高效的 $O(\log n)$ 查找、插入和删除操作,并保证元素按键排序。理解它们的特性、差异以及底层数据结构(红黑树)对于在 C++ 程序中选择和使用合适的容器至关重要。当允许重复键时,应使用它们的multi版本。在不需要顺序的场景下,哈希容器 (unordered_*) 通常是更快的选择。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 14:10:13

YOLOE官版镜像推理速度快1.4倍?实测结果来了

YOLOE官版镜像推理速度快1.4倍&#xff1f;实测结果来了 你有没有遇到过这样的情况&#xff1a;模型结构明明很轻量&#xff0c;参数量比YOLOv8还少&#xff0c;但一跑推理就卡在GPU显存加载上&#xff0c;预热时间长、首帧延迟高、批量处理吞吐上不去&#xff1f;更别提在开放…

作者头像 李华
网站建设 2026/10/14 22:34:26

企业级AI应用实战:Qwen3-VL多模态助手飞书集成

企业级AI应用实战&#xff1a;Qwen3-VL多模态助手飞书集成 你是否经历过这样的场景&#xff1f;市场部同事凌晨三点发来一张活动海报截图&#xff0c;问&#xff1a;“这个配色会不会太刺眼&#xff1f;”&#xff1b;客服团队每天要人工核对上百张用户上传的故障设备照片&…

作者头像 李华
网站建设 2026/10/15 15:31:30

Llama-3.2-3B生产环境:Ollama部署+K8s实现弹性扩缩容文本服务集群

Llama-3.2-3B生产环境&#xff1a;Ollama部署K8s实现弹性扩缩容文本服务集群 1. 为什么需要生产级的Llama-3.2-3B服务 你可能已经试过在本地用ollama run llama3.2:3b跑通一个对话&#xff0c;但那只是玩具。真正用在业务里&#xff0c;比如给客服系统提供实时回复、为内容平…

作者头像 李华
网站建设 2026/10/15 7:14:00

低成本微调方案:单卡RTX4090D跑通7B级别模型

低成本微调方案&#xff1a;单卡RTX4090D跑通7B级别模型 你是否也经历过这样的困扰&#xff1a;想微调一个7B级别的大模型&#xff0c;却发现显存不够、环境配置复杂、训练时间太长&#xff1f;动辄需要双卡A100、多卡并行、DeepSpeed Zero3配置——这些门槛让很多开发者望而却…

作者头像 李华
网站建设 2026/10/15 17:50:04

OpenMV动态阈值调整策略:实战中的应用示例

OpenMV动态阈值实战手记:当光照在变,你的阈值还在硬编码吗? 去年冬天调试一条食品包装产线时,我盯着OpenMV串口不停刷出的 [] 发了十分钟呆——明明摄像头正对着鲜红的番茄酱瓶盖, find_blobs() 却始终返回空列表。换灯、调角度、甚至把镜头擦了三遍,问题依旧。直到…

作者头像 李华