news 2026/5/5 22:56:07

C++ STL set与map深度解析

作者头像

张小明

前端开发工程师

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

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

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

  1. 有序性 (Sorted):

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

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

二、 std::set

  1. 定义与特性:

    • std::set是一个关联容器,专门用于存储唯一的键(Key)。在set中,键就是值本身
    • 每个元素在set中都是唯一的。尝试插入重复的元素会被忽略(插入操作返回一个pair<iterator, bool>,其中boolfalse表示插入失败)。
    • 元素一旦插入,不能被修改(因为修改可能破坏排序)。要“修改”一个元素,通常的做法是先删除旧元素,再插入新元素。
    • 元素类型 (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>boolfalse)。
    • 键 (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::multisetstd::multimap允许存储重复的键
  • 它们的接口与setmap非常相似,主要区别在于:
    • 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_setunordered_map(哈希表)。

七、 总结

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

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

SeqGPT-560M轻量高效部署:1.1GB模型在消费级RTX 3090上流畅运行

SeqGPT-560M轻量高效部署&#xff1a;1.1GB模型在消费级RTX 3090上流畅运行 你是不是也遇到过这样的问题&#xff1a;想快速验证一个文本理解任务&#xff0c;却要花半天搭环境、下载模型、写推理脚本&#xff1f;训练数据还没凑齐&#xff0c;显存已经爆了。今天要聊的这个模…

作者头像 李华
网站建设 2026/4/27 15:52:34

小白必看!灵毓秀-牧神-造相Z-Turbo文生图模型保姆级使用指南

小白必看&#xff01;灵毓秀-牧神-造相Z-Turbo文生图模型保姆级使用指南 前言&#xff1a; 最近在AI绘画圈里刷到一个特别有意思的小众模型——灵毓秀-牧神-造相Z-Turbo。它不是泛泛而谈的“古风美女”&#xff0c;而是专为《牧神记》原著粉丝打造的定制化文生图模型&#xff…

作者头像 李华
网站建设 2026/4/21 11:50:36

[工业自动化-33]:什么是线性自动控制系统与非线性自动控制系统?

我们用通俗易懂、生活化的方式来解释线性自动控制系统 和非线性自动控制系统 的区别。 &#x1f31f; 一句话总结&#xff1a; 线性系统&#xff1a;输入加倍&#xff0c;输出也加倍&#xff0c;行为“规矩”、可预测。 非线性系统&#xff1a;输入加倍&#xff0c;输出可能翻倍…

作者头像 李华
网站建设 2026/5/2 11:18:51

遇到报错别慌!GLM-TTS常见问题速查手册

遇到报错别慌&#xff01;GLM-TTS常见问题速查手册 你刚点下“ 开始合成”&#xff0c;页面却卡在加载状态&#xff1b; 上传了三段不同音色的参考音频&#xff0c;生成结果却一个比一个失真&#xff1b; 批量任务跑了一半突然中断&#xff0c;日志里只有一行红色报错&#xf…

作者头像 李华
网站建设 2026/5/5 15:27:06

为什么Youtu-2B部署总失败?镜像免配置教程来帮你

为什么Youtu-2B部署总失败&#xff1f;镜像免配置教程来帮你 1. 真实痛点&#xff1a;不是模型不行&#xff0c;是部署卡在“看不见的坑”里 你是不是也遇到过这些情况&#xff1f; 下载了Youtu-2B镜像&#xff0c;一启动就报错 CUDA out of memory&#xff0c;明明显卡有16…

作者头像 李华
网站建设 2026/4/20 7:32:27

一文说清vivado2019.2在Windows上的破解安装

Vivado 2019.2:在 Windows 上稳稳跑起来的硬核实践手记 去年帮一所地方高校的嵌入式实验室重装 FPGA 开发环境,三台 Win10 工作站,清一色 i7+32GB+512GB NVMe,结果两台卡在启动界面报 ERROR: [Common 17-345] Unable to get a license for feature Vivado_Suite ——不是…

作者头像 李华