吃透C++ STL map/set:从入门到实战,新手也能轻松上手
文章目录前言一、先搞懂map和set是什么核心区别在哪二、set使用详解去重排序一键搞定三、map使用详解键值映射高效查找四、map和set的常见避坑点新手必看五、map/set vs 其他容器怎么选六、总结前言在C STL标准模板库中map和set是最常用的关联式容器它们底层基于红黑树一种平衡二叉搜索树实现天生具备“自动排序”和“高效检索”的特性能极大简化我们的代码开发尤其在需要快速查找、去重、排序的场景中堪称“神器”。很多新手刚接触map和set时常常混淆两者的用途什么时候用set什么时候用map它们的接口有什么区别今天这篇博客全程以“实用”为核心不搞复杂理论只讲“怎么用、用在哪、避坑点”搭配极简实战代码新手跟着敲一遍就能轻松掌握map和set的核心用法。一、先搞懂map和set是什么核心区别在哪map和set同属关联式容器底层都是红黑树因此都具备O(log₂N)的插入、查找、删除效率且元素会自动排序但两者的核心用途和存储结构完全不同这是区分它们的关键。1. 核心定义与存储结构set集合仅存储“键key”不存储“值value”且不允许重复key元素会按照key的默认规则升序自动排序。可以理解为“去重排序”的容器比如存储一个班级的学号不重复且需要有序。map映射存储“键值对key-value”key唯一value可重复元素会按照key的默认规则升序自动排序。可以理解为“字典”key是“单词”value是“释义”通过key能快速找到对应的value比如存储学生的学号key和姓名value。2. 核心区别一张表看懂特性setmap存储内容仅key键key-value键值对key是否唯一是不允许重复是不允许重复排序依据按key自动排序按key自动排序核心用途去重、排序、快速查找某个值是否存在键值映射、通过key快速获取value、排序底层结构红黑树平衡二叉搜索树红黑树平衡二叉搜索树补充STL还提供了multiset和multimap允许key重复用法和set、map基本一致仅在“key唯一性”上有差异本文重点讲解最常用的set和mapkey唯一。二、set使用详解去重排序一键搞定set的用法非常简单核心围绕“插入、查找、删除、遍历”四个操作无需手动排序插入后元素自动按升序排列且自动去重。1. 头文件与定义使用set必须包含头文件set定义时需指定存储的数据类型即key的类型默认排序规则为升序#include iostream #include set // set的头文件 using namespace std; // 定义set存储int类型默认升序 setint s; // 可选定义降序set需指定排序规则 setint, greaterint s_desc; // 降序排列2. 核心接口实战常用必掌握set的接口不多重点掌握以下6个就能应对大部分场景搭配代码示例理解更直观1插入元素insert()插入单个元素自动去重、自动排序也可插入区间内的元素。setint s; // 插入单个元素 s.insert(5); s.insert(2); s.insert(8); s.insert(2); // 重复元素插入失败不会报错 // 插入区间元素从数组插入 int arr[] {3, 7, 1}; s.insert(arr, arr 3); // 此时set中的元素1, 2, 3, 5, 7, 8自动升序、去重2查找元素find()查找指定key是否存在返回迭代器找到则返回对应元素的迭代器未找到则返回s.end()set的末尾迭代器指向元素后面的位置。setint s {1, 2, 3, 5, 7, 8}; // 查找key5 auto it s.find(5); if (it ! s.end()) { cout 找到元素 *it endl; // 输出找到元素5 } else { cout 未找到元素 endl; } // 查找key4 it s.find(4); if (it s.end()) { cout 未找到元素 endl; // 输出未找到元素 }3删除元素erase()有三种删除方式按迭代器删除、按key删除、删除区间内元素最常用的是前两种。setint s {1, 2, 3, 5, 7, 8}; // 1. 按key删除 s.erase(3); // 删除key3的元素返回删除的个数0或1 // 2. 按迭代器删除先找到元素再删除 auto it s.find(5); if (it ! s.end()) { s.erase(it); // 删除key5的元素 } // 3. 删除区间元素删除从begin到end的所有元素即清空set // s.erase(s.begin(), s.end()); // 此时set中的元素1, 2, 7, 84遍历元素迭代器/范围forset支持迭代器遍历和C11后的范围for遍历遍历顺序就是元素的排序顺序默认升序。setint s {1, 2, 7, 8}; // 1. 迭代器遍历 for (setint::iterator it s.begin(); it ! s.end(); it) { cout *it ; // 输出1 2 7 8 } cout endl; // 2. 范围for遍历C11及以上更简洁 for (auto num : s) { cout num ; // 输出1 2 7 8 } cout endl;5获取元素个数size()setint s {1, 2, 7, 8}; cout set元素个数 s.size() endl; // 输出46清空元素clear()setint s {1, 2, 7, 8}; s.clear(); // 清空所有元素 cout 清空后元素个数 s.size() endl; // 输出03. set实战场景去重排序最典型的场景对一组数据去重并排序无需手动写排序和去重逻辑set一键搞定。#include iostream #include set using namespace std; int main() { // 一组重复、无序的数据 int arr[] {5, 2, 8, 2, 3, 7, 1, 5, 9}; int n sizeof(arr) / sizeof(arr[0]); // 用set去重排序 setint s(arr, arr n); // 输出去重排序后的结果 cout 去重排序后; for (auto num : s) { cout num ; // 输出1 2 3 5 7 8 9 } return 0; }三、map使用详解键值映射高效查找map的核心是“key-value”映射key唯一且排序通过key能快速找到对应的value用法和set类似但多了对value的操作重点掌握“键值对的插入、访问、修改”。1. 头文件与定义使用map必须包含头文件map定义时需指定key和value的类型默认按key升序排序#include iostream #include map // map的头文件 using namespace std; // 定义mapkey为int学号value为string姓名默认升序 mapint, string m; // 可选定义降序map mapint, string, greaterint m_desc; // 按key降序排列2. 核心接口实战常用必掌握map的接口和set有很多相似之处但增加了对value的操作重点掌握以下7个1插入键值对insert() / 下标[]有两种常用插入方式insert()插入pair对象下标[]直接赋值更简洁注意下标[]若key不存在会自动插入该keyvalue为默认值如string为空串。mapint, string m; // 方式1insert()插入pair对象推荐可判断是否插入成功 m.insert(pairint, string(101, 张三)); m.insert(make_pair(102, 李四)); // make_pair更简洁 // 插入重复key返回pair迭代器, boolbool为false表示插入失败 auto ret m.insert(make_pair(101, 王五)); if (!ret.second) { cout key101已存在插入失败 endl; } // 方式2下标[]插入简洁但会自动创建不存在的key m[103] 赵六; // 插入key103value赵六 m[104] 孙七; // 此时map中的键值对按key升序(101,张三), (102,李四), (103,赵六), (104,孙七)2访问value下标[] / find() 迭代器两种访问方式下标[]更简洁但要注意若key不存在会自动插入该keyvalue为默认值find()更安全不会自动插入。mapint, string m {{101, 张三}, {102, 李四}, {103, 赵六}}; // 方式1下标[]访问简洁 cout 101号学生 m[101] endl; // 输出101号学生张三 cout 104号学生 m[104] endl; // key104不存在自动插入value为空串输出空 // 方式2find() 迭代器安全推荐 auto it m.find(102); if (it ! m.end()) { cout 102号学生 it-second endl; // 输出102号学生李四 } // 访问不存在的key不会自动插入 it m.find(105); if (it m.end()) { cout 105号学生不存在 endl; }注意map的迭代器指向的是“键值对pair”通过it-first访问keyit-second访问value。3修改value下标[] / find() 迭代器修改value的前提是key已存在两种方式均可下标[]更简洁。mapint, string m {{101, 张三}, {102, 李四}}; // 方式1下标[]修改 m[101] 张三丰; // 将101号的value改为张三丰 // 方式2find() 迭代器修改 auto it m.find(102); if (it ! m.end()) { it-second 李世民; // 将102号的value改为李世民 } // 输出修改后的值 cout 101号 m[101] endl; // 输出101号张三丰 cout 102号 m[102] endl; // 输出102号李世民4删除元素erase()和set用法一致支持按key删除、按迭代器删除、删除区间元素。mapint, string m {{101, 张三}, {102, 李四}, {103, 赵六}}; // 1. 按key删除 m.erase(102); // 删除key102的键值对 // 2. 按迭代器删除 auto it m.find(103); if (it ! m.end()) { m.erase(it); // 删除key103的键值对 } // 3. 删除区间元素清空map // m.erase(m.begin(), m.end()); // 此时map中仅剩余(101, 张三)5遍历元素迭代器/范围for遍历顺序按key的排序规则重点关注键值对的访问方式。mapint, string m {{101, 张三}, {102, 李四}, {103, 赵六}}; // 1. 迭代器遍历 for (mapint, string::iterator it m.begin(); it ! m.end(); it) { // it-first是keyit-second是value cout 学号 it-first 姓名 it-second endl; } // 2. 范围for遍历C11及以上 for (auto pair : m) { cout 学号 pair.first 姓名 pair.second endl; }6获取元素个数size()7清空元素clear()和set用法完全一致直接调用接口即可。mapint, string m {{101, 张三}, {102, 李四}}; cout 元素个数 m.size() endl; // 输出2 m.clear(); cout 清空后个数 m.size() endl; // 输出03. map实战场景键值映射最典型的场景存储键值对通过key快速查找value比如存储学生学号与成绩、单词与释义等。#include iostream #include map #include string using namespace std; int main() { // 存储学生学号key和成绩value mapint, int student_score; // 插入数据 student_score.insert(make_pair(101, 95)); student_score.insert(make_pair(102, 88)); student_score[103] 92; student_score[104] 79; // 查找并输出102号学生的成绩 auto it student_score.find(102); if (it ! student_score.end()) { cout 102号学生成绩 it-second endl; // 输出88 } // 修改104号学生的成绩 student_score[104] 85; cout 修改后104号学生成绩 student_score[104] endl; // 输出85 // 遍历所有学生成绩按学号升序 cout 所有学生成绩 endl; for (auto pair : student_score) { cout 学号 pair.first 成绩 pair.second endl; } return 0; }四、map和set的常见避坑点新手必看误区1map的下标[]会自动插入不存在的key——这是新手最容易踩的坑如果只是想“查找”value不要用下标[]用find()更安全避免误插入多余的key。误区2set和map可以直接修改元素——不可以set的元素是const类型不能修改否则会破坏排序和唯一性map只能修改value不能修改key修改key会破坏排序和唯一性。误区3插入重复key会报错——不会报错set和map插入重复key时插入会失败但程序不会崩溃可通过insert()的返回值判断是否插入成功。误区4set和map的迭代器可以随意加减——不可以它们的迭代器是“双向迭代器”只能用、--操作不能用1、-1和vector的随机迭代器不同。误区5底层是红黑树所以插入效率一定很高——红黑树插入时会自动平衡效率是O(log₂N)但比vector的尾插O(1)慢适合“频繁查找、删除”的场景不适合“频繁尾插”的场景。五、map/set vs 其他容器怎么选很多新手不知道什么时候用map/set什么时候用vector/list这里给出明确的选择依据需要去重、排序优先用set需要键值映射、快速通过key找value优先用map需要频繁随机访问、尾插尾删优先用vector需要频繁插入、删除非尾端且不需要排序优先用list需要允许key重复用multiset/multimap用法和set/map基本一致。六、总结map和set是C STL中最实用的关联式容器核心优势在于“自动排序”和“O(log₂N)的高效操作”底层红黑树的实现让它们无需我们手动管理排序和平衡极大提升开发效率。对于新手来说重点掌握1. 区分map和setset存key去重排序map存key-value键值映射2. 核心接口insert()、find()、erase()、size()、clear()以及map的下标访问3. 避坑点map下标[]的自动插入、不可修改set的元素和map的key。其实map和set的用法并不复杂多敲几遍实战代码熟悉接口的使用场景就能轻松掌握。它们在面试和实际开发中都非常常用学好它们能让你的代码更简洁、高效、易维护。