news 2026/5/1 9:03:47

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/1 4:03:04

嵌入式工控机如何接入远程USB摄像头:详细配置流程

嵌入式工控机如何真正“用好”远程USB摄像头:从踩坑到稳如磐石的实战手记 去年在某汽车焊装车间部署视觉定位系统时,我们把一台IMX8MP工控机塞进控制柜,而UVC摄像头却必须装在机械臂末端——离柜体足足7米远。现场布线师傅甩来一句:“USB线?别想了,3米都抖。”那一刻我才…

作者头像 李华
网站建设 2026/5/1 1:28:50

关键词匹配不准?试试MGeo地理语义对齐能力

关键词匹配不准&#xff1f;试试MGeo地理语义对齐能力 地址匹配这件事&#xff0c;听起来简单&#xff0c;做起来却常让人抓狂。你是不是也遇到过这些情况&#xff1a; 用户搜“杭州西湖文三路159号”&#xff0c;系统却只召回带“文三路”的结果&#xff0c;漏掉了“西湖区文…

作者头像 李华
网站建设 2026/5/1 5:03:53

物联网设备中nanopb与Protobuf对比:通俗解释

nanopb:在裸机MCU上跑通Protobuf的硬核实践 你有没有遇到过这样的场景? 在调试一款基于STM32L0的电池供电温湿度节点时,发现用 cJSON 解析一个 80 字节的 JSON 报文,光是 malloc 就占了 1.2KB 堆空间,而整块芯片只有 8KB RAM——更糟的是,三天后设备突然死机,串口只吐…

作者头像 李华
网站建设 2026/4/30 13:05:10

MusePublic CFG Scale调优:8-12区间对人物神态与背景协调性的实测

MusePublic CFG Scale调优&#xff1a;8-12区间对人物神态与背景协调性的实测 1. 为什么CFG Scale这个参数值得你花10分钟细看 你有没有遇到过这样的情况&#xff1a; 输入了一段精心打磨的提示词——“一位穿墨绿色丝绒长裙的东方女性&#xff0c;侧身站在雨后梧桐街角&…

作者头像 李华
网站建设 2026/5/1 7:23:21

基于ESP32的u8g2硬件抽象层实现:手把手教程

基于ESP32的u8g2硬件抽象层&#xff1a;从踩坑到量产的实战手记去年冬天调试一块SH1107 SPI OLED时&#xff0c;我连续三天卡在“屏幕只亮左半边”的问题上。示波器抓到CS信号毛刺&#xff0c;逻辑分析仪看到DC线在SPI传输中途被意外拉低——那一刻我才真正意识到&#xff1a;u…

作者头像 李华
网站建设 2026/5/1 7:20:44

分布式数据恢复—Ceph+TiDB数据恢复报告

一、Ceph故障表现 故障情况&#xff1a;客户设备为Ceph分布式存储系统&#xff0c;采用RBD&#xff08;RADOS Block Device&#xff09;作为块存储服务。Ceph集群由多个OSD&#xff08;Object Storage Daemon&#xff09;节点组成&#xff0c;数据通过CRUSH算法分布存储在多个物…

作者头像 李华