开篇介绍:
hello 大家,那么在本篇博客中,我们将学习C++中unordered_map的使用方法,前面我们学习了unordered_set的用法,那么其实unordered_map的用法也差不多,但是依然值得我们去了解一下其各个接口的用处。
一、unordered_map 核心特性铺垫
在了解函数接口前,先明确 unordered_map 的核心属性(与 map 对比,突出哈希表特性):
-
底层是哈希表(哈希桶+链表/红黑树,不同编译器实现略有差异,如 GCC 用链表,VS 用红黑树解决哈希冲突),增删查(按 key)平均效率 O(1),最坏情况 O(N)(极端哈希冲突);
-
元素按 key 的哈希值存储,遍历无序(与插入顺序、key 大小均无关);
-
「核心特性」key 唯一(自带去重),一个 key 对应一个 value;
-
存储的元素是 pair<const Key, T> 结构体(first=key,second=value),与 map 一致;
-
迭代器是单向迭代器(仅支持 ++,不支持 --/随机访问),这是哈希表结构决定的;
-
不可修改 key(pair 的 first 是 const 类型,修改会破坏哈希表结构),但可自由修改 value(second 可读写)。
补充:unordered_map 对 Key 类型的要求与 map 不同:1. map 要求 Key 支持小于比较(重载 operator<);2. unordered_map 要求 Key 支持:① 转换为整形哈希值(默认提供基础类型哈希函数,自定义类型需自定义);② 等于比较(重载 operator==)。
二、构造函数(创建 unordered_map 容器)
核心作用:初始化 unordered_map 容器,存储 key-value 键值对,可指定初始哈希桶数、自定义哈希函数和比较函数,是使用 unordered_map 的第一步。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 无参构造 unordered_map<Key, T> um; | 创建空的 unordered_map,底层哈希表使用默认桶数(VS 默认 8,GCC 默认 11) | 无参数;默认哈希函数 hash<Key>(),默认比较函数 equal_to<Key>() | 后续通过 insert/\[\] 插入键值对 | Key 需为基础类型(如 int、string),STL 已提供默认哈希函数;自定义类型需手动指定哈希和比较函数 | unordered_map<int, string> um1; um1.insert({1, "apple"});(结果:{1:"apple"},无序) |
| 自定义哈希/比较 unordered_map<Key, T, Hash, Pred> um; | 创建空的 unordered_map,使用自定义哈希函数 Hash 和等于比较函数 Pred | Hash:哈希函数类(需重载 operator(),返回 size_t);Pred:比较函数类(重载 operator(),返回 bool) | Key 为自定义类型(如结构体、类),无法使用默认哈希/比较函数 | 哈希函数需保证"相同 Key 哈希值相同",比较函数需准确判断 Key 相等;两者必须配套使用 | // 自定义结构体struct Person { string name; int age; };// 自定义哈希函数struct HashPerson { size_t operator()(const Person& p) const { return hash<string>()(p.name) ^ hash<int>()(p.age); } };// 自定义比较函数struct EqualPerson { bool operator()(const Person& p1, const Person& p2) const { return p1.name == p2.name && p1.age == p2.age; } };unordered_map<Person, string, HashPerson, EqualPerson> um2; |
| 迭代器区间 unordered_map<Key, T> um(first, last); | 从其他键值对容器的迭代器区间 [first, last) 拷贝元素初始化 | 参数:源容器迭代器区间(元素需为 pair<Key, T> 类型) | 从 vector、list 等容器批量导入键值对 | 自动按 Key 去重(重复 Key 仅保留第一个);导入后元素无序存储;区间为左闭右开 | vector<pair<int, string>> v={{3:"orange"}, {2:"banana"}, {3:"pear"}};unordered_map<int, string> um3(v.begin(), v.end());(结果:{2:"banana", 3:"orange"},无序) |
| 初始化列表 unordered_map<Key, T> um({{k1,v1}, {k2,v2}, ...}); | 用 {} 包裹的键值对直接初始化,支持 {key, value} 隐式转 pair | 参数:键值对列表(可混用 {k,v}、make_pair(k,v)、pair(k,v)) | 创建时已知所有键值对,最常用、最简洁的初始化方式 | Key 重复会自动去重;初始化后元素无序;C++11+ 特性 | unordered_map<int, string> um4={{1:"apple"}, make_pair(2,"banana"), pair(3,"orange")};(结果无序,去重) |
| 拷贝构造 unordered_map<Key, T> um(other); | 复制另一个 unordered_map 的所有键值对、哈希函数和比较函数 | 参数:已存在的 unordered_map 容器 | 复用已有容器的数据和配置 | 原容器不变;复制后仍保持无序和 Key 唯一特性 | unordered_map<int, string> um5(um4);(um5 与 um4 元素相同,无序) |
| 移动构造 unordered_map<Key, T> um(move(other)); | 接管另一个 unordered_map 的底层资源,原容器变为空 | 参数:右值引用(通过 std::move 转换左值为右值) | 需要快速复用临时 unordered_map 的资源,避免拷贝开销 | 移动后原容器不可再使用(size 为 0,empty 为 true);C++11+ 特性 | unordered_map<int, string> um6(move(um5)); cout << um5.empty();(输出 true,um6 拥有原 um5 元素) |
| 指定初始桶数 unordered_map<Key, T> um(n); | 创建空容器,指定初始哈希桶数量为 n | 参数:n 为初始桶数(size_t 类型) | 批量插入大量元素前,提前分配桶空间,减少后续扩容(rehash)次数,提升性能 | 若 n 小于编译器默认桶数,仍使用默认值;桶数通常会调整为 2 的幂次方(多数编译器实现) | unordered_map<int, string> um7(100); // 初始桶数 100,后续插入大量元素减少 rehash |
三、迭代器函数(遍历 unordered_map 元素)
核心作用:获取遍历起点/终点,支持正向遍历(仅正向),迭代器解引用后为 pair<const Key, T> 类型,需通过 first/second 访问 key/value。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| begin() / end() | begin() 指向第一个键值对,end() 指向尾后位置(不指向任何元素) | 返回单向迭代器(unordered_map<Key,T>::iterator) | 正向遍历所有元素 | end() 不可解引用;迭代器仅支持 ++,不支持 --/随机访问;可通过 it->second 修改 value | unordered_map<int, string> um={{1:"apple"}, 2:"banana"};for (auto it=um.begin(); it!=um.end(); ++it) { cout << it->first << ":" << it->second; // 输出顺序不确定,如 2:banana 1:apple} |
| cbegin() / cend() | 功能同 begin()/end(),但返回只读迭代器 | 返回常量单向迭代器(const_iterator) | 仅读取元素,不修改 value | 不可通过该迭代器修改 it->second;key 始终不可修改;更安全的只读遍历 | for (auto it=um.cbegin(); it!=um.cend(); ++it) { cout << it->first << ":" << it->second; // 仅读取,无法修改} |
重要差异:unordered_map 不支持反向迭代器(rbegin()/rend()),而 map 支持。这是因为哈希表的存储结构不具备反向遍历的天然条件,若需反向遍历,需先将元素存入 vector 再排序。
四、插入函数(向 unordered_map 添加键值对)
核心作用:向容器添加 pair 类型键值对,自动按 Key 去重(重复 Key 插入失败),支持单个、批量插入,插入后元素按哈希值无序存储。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 单个插入 pair<iterator, bool> insert(const pair<Key,T>& val); | 插入单个键值对,Key 不存在则成功,存在则失败 | 返回 pair:- first:指向插入/已存在 Key 的迭代器;- second:插入成功标志(true=成功,false=重复) | 插入单个键值对,需判断是否插入成功 | 重复 Key 插入失败,first 指向已有 Key 的迭代器;value 不覆盖;支持 {k,v} 隐式转 pair | unordered_map<int, string> um;auto ret1 = um.insert({1, "apple"}); cout << ret1.second;(输出 true)auto ret2 = um.insert({1, "red apple"}); cout << ret2.second;(输出 false,value 仍为 "apple") |
| 位置提示插入 iterator insert(iterator pos, const pair<Key,T>& val); | 传入提示位置插入键值对(哈希表忽略提示,仍按哈希值存储) | 参数:提示迭代器 + 键值对;返回插入/已存在 Key 的迭代器(无 bool 值) | 大致知道元素存储位置,理论上优化哈希查找效率 | 提示位置不影响最终存储位置;实际效率提升有限,多数场景不推荐使用 | auto it = um.insert(um.begin(), {2, "banana"});cout << it->second;(输出 "banana") |
| 批量插入(区间)insert(first, last); | 插入其他容器迭代器区间 [first, last) 内的所有键值对 | 参数:源容器迭代器区间(元素需为 pair 类型);无返回值 | 从 vector、list 等容器批量导入键值对 | 自动按 Key 去重;区间为左闭右开;导入后元素无序 | vector<pair<int, string>> v={{3:"orange"}, {2:"pear"}, {4:"grape"}};um.insert(v.begin(), v.end());(结果去重,无序) |
| 批量插入(列表)insert({{k1,v1}, {k2,v2}, ...}); | 用 {} 包裹多个键值对,一次性插入 | 参数:初始化列表;无返回值 | 已知多个插入元素,简洁高效的批量导入方式 | 自动按 Key 去重;C++11+ 特性;支持直接写 {k,v} 列表 | um.insert({{3:"orange"}, {4:"grape"}, {2:"pear"}});// 重复 Key=2 仅保留原有值 |
| 原地构造 emplace(args...); | 直接在容器内构造键值对,避免 pair 的拷贝开销 | 参数:构造 pair 所需的参数(Key 和 T 的构造参数);返回值同 insert 单个插入 | 插入自定义类型 Key/Value,追求更高性能(避免拷贝) | C++11+ 特性;参数需与 pair<Key,T> 的构造函数匹配 | // 构造 pair<int, string>,无需手动创建 pairauto ret = um.emplace(5, "mango");cout << ret.second;(输出 true,插入成功) |
五、\[\] 运算符重载(unordered_map 核心便捷接口)
核心作用:通过 Key 快速访问或修改对应的 Value,若 Key 不存在则自动插入该 Key(Value 为默认构造值),是 unordered_map 最常用的便捷接口,用法与 map 的 \[\] 高度一致,但底层实现为哈希查找。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 |
|---|---|---|---|---|
| T& operator\[\](const Key& key); | 1. 若 Key 存在:返回对应 Value 的左值引用(可修改);2. 若 Key 不存在:自动插入 {key, T()}(Value 为默认值,如 int=0、string=""),并返回新 Value 的引用。 | 参数:要访问的 Key;返回值:Value 的左值引用(T&) | 快速访问/修改 Value;允许自动插入新 Key 的场景 | 1. 必然触发"不存在则插入",可能导致意外的 Key 插入;2. 仅能修改 Value,Key 仍不可改;3. 需确保 Value 类型支持默认构造(自定义类型需提供默认构造函数);4. 常量 unordered_map 不可用 \[\](可能修改容器)。 |
关键细节补充:\[\] 与 insert/find 的区别
-
与 insert 的区别: - \[\]:侧重"访问/修改",无条件插入不存在的 Key ,直接返回 Value 引用(无法判断是否为新插入); - insert:侧重"插入",Key 存在则插入失败 ,返回 pair<iterator, bool>(可明确判断插入结果)。 示例对比:
unordered_map<int, string> um; ``um.insert({1, "apple"}); // Key=1 不存在,插入成功 ``um.insert({1, "red apple"}); // Key=1 已存在,插入失败(仍为 "apple") `` ``um[1] = "red apple"; // 直接修改 Value,覆盖为 "red apple" ``um[2] = "banana"; // 自动插入 Key=2,Value 为 "banana" -
与 find 的区别: - \[\]:必然访问 Value,不存在则插入新 Key; - find:仅查找 Key,不存在则返回 end()(不插入),更适合"仅判断 Key 是否存在"的场景。 示例对比:
unordered_map<int, string> um={``{1, "apple"}}; ``auto it = um.find(2); // Key=2 不存在,返回 um.end()(无插入) ``if (it == um.end()) cout << "Key=2 不存在"; `` ``um[2]; // 无赋值操作,但已自动插入 {2, ""},um.size() 变为 2
六、删除函数(从 unordered_map 移除键值对)
核心作用:按 Key、位置或区间删除键值对,删除后哈希表会自动调整桶结构,保证后续操作高效。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 按位置删除 iterator erase(iterator pos); | 删除迭代器指向的键值对,返回下一个元素的迭代器 | 参数:键值对迭代器;返回下一个元素的迭代器 | 已知元素的迭代器位置(如 find 找到后删除) | 不可传入 end() 迭代器(非法操作);删除前需通过 find 确认元素存在 | auto it = um.find(2);if (it != um.end()) { um.erase(it); // 删除 Key=2 的键值对} |
| 按 Key 删除 size_type erase(const Key& key); | 删除 Value 为 key 的键值对,返回删除的个数 | 参数:要删除的 Key;返回 size_t 类型(0=未找到,1=删除成功) | 知道 Key,直接删除对应键值对(最常用) | Key 不存在时返回 0,不报错;因 Key 唯一,最多删除 1 个元素 | size_t del = um.erase(3);cout << del;(存在 Key=3 则返回 1,否则 0) |
| 按区间删除 erase(first, last); | 删除迭代器区间 [first, last) 内的所有键值对,返回下一个元素的迭代器 | 参数:迭代器区间;返回下一个元素的迭代器 | 批量删除一段连续元素(按迭代器遍历顺序) | 左闭右开区间;因元素无序,"连续"是迭代器遍历顺序的连续,非 Key 大小连续 | auto first = um.begin();auto last = um.find(4);if (last != um.end()) { um.erase(first, last); // 删除从 begin 到 Key=4 之前的元素} |
| 清空 clear(); | 删除容器中所有键值对,清空容器 | 无参数;无返回值 | 需清空整个容器,重新填充数据 | 清空后 size() 为 0,empty() 为 true;哈希表的桶结构保留(不会删除桶) | um.clear();cout << um.empty();(输出 true) |
七、查找函数(查询 unordered_map 中的键值对)
核心作用:按 Key 查询元素是否存在、获取元素位置,基于哈希表实现,平均效率 O(1),远高于 map 的 O(logN),是 unordered_map 的核心优势。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 精准查找 iterator find(const Key& key); | 查找 Value 为 key 的键值对,返回其迭代器 | 参数:要查找的 Key;返回迭代器(找到=指向键值对,未找到=end()) | 确认 Key 存在并定位其位置,后续修改 Value 或删除 | 平均效率 O(1),远优于算法库的 find 函数(O(N));仅按 Key 匹配,与 Value 无关 | auto it = um.find(1);if (it != um.end()) { cout << "找到:" << it->first << ":" << it->second;} |
| 统计个数 size_type count(const Key& key); | 统计 Value 为 key 的键值对个数 | 参数:要统计的 Key;返回 size_t 类型(0=不存在,1=存在) | 快速判断 Key 是否存在(比 find 更简洁) | 因 Key 唯一,返回值只有 0 或 1;本质是哈希查找,效率与 find 一致 | if (um.count(2)) { cout << "Key=2 存在";} else { cout << "Key=2 不存在";} |
八、哈希表特有操作(桶操作)
核心作用:直接操作底层哈希表的"桶"结构,辅助优化性能(如提前分配桶数、监控哈希冲突),是 unordered_map 独有的接口(map 无此类接口)。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| bucket_count() | 返回当前哈希表的桶数量 | 无参数;返回 size_t 类型 | 查看当前哈希表的桶配置,评估扩容需求 | 桶数量决定哈希冲突概率,桶数越多,冲突概率越低 | cout << "当前桶数:" << um.bucket_count();(输出示例:8) |
| bucket(key) | 返回 Key 对应的键值对所在的桶编号(从 0 开始) | 参数:要查询的 Key;返回 size_t 类型(桶编号) | 定位 Key 所在的桶,分析哈希冲突情况 | 相同 Key 必然在同一个桶;不同 Key 可能因哈希冲突在同一个桶 | size_t bucket_num = um.bucket(1);cout << "Key=1 所在桶:" << bucket_num; |
| bucket_size(n) | 返回第 n 个桶中的键值对个数 | 参数:桶编号 n;返回 size_t 类型 | 查看某个桶的元素数量,判断哈希冲突严重程度(桶内元素越多,冲突越严重) | 桶编号范围:0 ~ bucket_count()-1;桶内元素过多会降低查询效率 | cout << "桶" << bucket_num << "元素数:" << um.bucket_size(bucket_num); |
| load_factor() | 返回当前负载因子(size / bucket_count,浮点数) | 无参数;返回 float 类型 | 评估哈希表的填充程度,判断是否需要手动扩容 | 负载因子越高,哈希冲突概率越高;超过 max_load_factor() 会触发自动 rehash(扩容) | cout << "当前负载因子:" << um.load_factor();(示例:5/8=0.625) |
| max_load_factor() | 返回/设置最大负载因子(默认 1.0) | 无参数时返回 float 类型;有参数时设置为指定浮点数(如 0.8) | 调整哈希表的扩容阈值,优化性能(降低负载因子减少冲突) | 设置后,当 load_factor() 超过该值时触发 rehash;值越小,桶数越多,冲突越少,但内存开销越大 | cout << "默认最大负载因子:" << um.max_load_factor();(输出 1.0)um.max_load_factor(0.8f); // 设置为 0.8 |
| rehash(n) | 强制设置桶数量为 n(n ≥ 当前 bucket_count()) | 参数:目标桶数 n;无返回值 | 批量插入大量元素前,手动扩容桶数,减少自动 rehash 次数 | 若 n 小于当前桶数,不生效;编译器可能会将 n 调整为 2 的幂次方 | um.rehash(100); // 强制将桶数设置为 100 |
| reserve(n) | 预留空间,确保桶数足够存储 n 个键值对而不触发 rehash | 参数:预期存储的元素个数 n;无返回值 | 明确知道要插入的元素数量,提前预留空间,优化性能 | 底层会根据 max_load_factor() 计算所需桶数(n / max_load_factor());比 rehash 更智能(无需手动计算桶数) | um.reserve(80); // 确保能存储 80 个元素而不 rehash(基于 0.8 的负载因子,桶数至少 100) |
九、其他常用函数
核心作用:辅助操作 unordered_map 容器,补充核心接口功能,与 map 用法一致。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| size() | 返回当前容器中的键值对个数 | 无参数;返回 size_t 类型(非负整数) | 获取容器中元素的数量 | 不可用于判断空容器(推荐用 empty(),更高效) | cout << "元素个数:" << um.size();(输出示例:3) |
| empty() | 判断容器是否为空(无元素) | 无参数;返回 bool 类型(true=空,false=非空) | 避免对空容器进行遍历、删除等操作(防止崩溃) | 比 size() == 0 更高效(直接判断标志位,无需计算元素个数) | if (!um.empty()) { /* 遍历容器 */ } |
| max_size() | 返回理论上可存储的最大元素数 | 无参数;返回 size_t 类型(极大值,取决于系统和内存) | 评估容器的最大存储能力(实际开发中极少用到) | 仅为理论值,实际存储量受内存限制 | cout << "最大可存储元素数:" << um.max_size(); |
| swap(unordered_map& x); | 交换两个 unordered_map 的内容、哈希函数和比较函数 | 参数:另一个同类型的 unordered_map 容器;无返回值 | 快速交换两个容器的数据,避免手动拷贝 | 时间复杂度 O(1)(仅交换底层指针);交换后两个容器的状态完全互换 | unordered_map<int, string> um1={{1:"a"}}, um2={{2:"b"}};um1.swap(um2); // um1 变为 {{2:"b"}}, um2 变为 {{1:"a"}} |
十、unordered_map 与 map 核心差异总结
为了让大家更清晰地掌握两者的适用场景,这里整理了核心差异对比表:
| 对比维度 | unordered_map | map |
|---|---|---|
| 底层结构 | 哈希表 | 红黑树(平衡二叉搜索树) |
| Key 要求 | 支持哈希(hash<Key>)和等于比较(==) | 支持小于比较(<) |
| 遍历顺序 | 无序(按哈希值存储) | 有序(按 Key 大小升序,中序遍历) |
| 迭代器类型 | 单向迭代器(仅支持 ++) | 双向迭代器(支持 ++/--) |
| 增删查效率 | 平均 O(1),最坏 O(N)(极端哈希冲突) | 稳定 O(logN) |
| 内存开销 | 较高(哈希表需要桶空间,存在内存碎片) | 较低(红黑树结构紧凑,无额外桶开销) |
| 特有接口 | 桶操作(bucket_count()、rehash() 等) | 范围查找(lower_bound()、upper_bound() 等) |
| 适用场景 | 高频增删查、无需有序遍历、Key 为基础类型 | 需要有序遍历、范围查询、Key 为自定义类型(易实现 <) |
十一、unordered_multimap 补充说明
unordered_multimap 是 unordered_map 的"多键版本",核心特性与 unordered_map 一致,唯一差异是 Key 可重复(一个 Key 可对应多个 Value),接口用法基本一致,仅以下几点差异:
-
插入:单个插入返回 iterator(无 bool 值,因 Key 可重复,插入必然成功);
-
删除:按 Key 删除时,会删除所有对应 Key 的键值对,返回实际删除个数(≥1);
-
查找:find() 返回中序遍历第一个匹配 Key 的迭代器;count() 返回 Key 的重复次数;equal_range() 是获取所有重复 Key 键值对的最优方式(返回 pair<iterator, iterator>,表示重复 Key 的范围);
-
无 \[\] 运算符:因 Key 可重复,\[\] 无法确定操作哪个 Value,故不支持。
示例代码(核心用法):
cpp
// 定义unordered_multimap并初始化(Key可重复)
unordered_multimap<int, string> umm = {
{1, "apple"},
{1, "red apple"},
{2, "banana"}
};
// 遍历所有重复 Key=1 的元素
// equal_range返回pair<iterator, iterator>,表示重复Key的迭代器范围
auto range = umm.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
cout << it->first << ":" << it->second << " ";
// 输出结果:1:apple 1:red apple
}
// 按 Key 删除所有重复元素(删除Key=1的全部键值对)
size_t del = umm.erase(1);
cout << "删除个数:" << del;
// 输出结果:2(成功删除2个Key=1的键值对)
示例代码:unordered_map 完整实操
cpp
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
// 用于异常捕获(at()接口)
#include <stdexcept>
using namespace std;
//那么本文件中我们就来了解一下unordered_map的使用
//其实和map的使用差不多
/*
* 补充:unordered_map与map的核心差异详解
* 1. 使用层面的一致性
* unordered_map支持增加、删除、查找、修改value等核心操作,其基础接口(insert()/erase()/find()等)
* 的调用语法、参数传递方式与map完全一致;
*
* 2. 第一个核心差异:对Key类型的要求不同
* - map对Key的要求:与set一致,依赖红黑树实现,要求Key支持小于比较(重载operator<);
* - unordered_map对Key的要求:与unordered_set一致,依赖哈希表实现,要求Key支持:
* ① 转换为整形哈希值(自定义类型需自定义哈希函数);
* ② 等于比较(重载operator==);
* - 补充:unordered_map的value无特殊要求,仅Key需满足上述条件。
*
* 3. 第二个核心差异:迭代器特性与遍历结果不同
* - 迭代器类型差异:map的iterator是双向迭代器(支持++/--),unordered_map是单向迭代器(仅支持++);
* - 遍历结果特性差异:
* ① map:底层红黑树,按Key大小有序存储,遍历呈现"有序+key去重"(value可任意);
* ② unordered_map:底层哈希表,遍历无序,仅"key去重+value可修改"。
*
* 4. 第三个核心差异:性能表现不同
* - 整体趋势:unordered_map增删查(按key)效率优于map(平均O(1) vs O(logN));
* - 例外场景:若需要有序遍历、或数据量极小、或哈希冲突极端严重,map性能可能更稳定。
*/
int main() {
/************************ 一、构造函数:创建unordered_map对象 ************************/
cout << "===== 1. 构造函数示例 =====" << endl;
// 1. 无参构造:创建空的unordered_map,底层哈希表默认桶数(VS默认8,GCC默认11)
// 特性:存储<int, string>类型的键值对,key=int(不可修改),value=string(可修改)
// 默认哈希函数hash<int>()(针对key),默认等于比较equal_to<int>()(针对key)
unordered_map<int, string> um1;
cout << "um1(无参构造)是否为空:" << boolalpha << um1.empty() << endl; // 输出true
// 2. 迭代器区间构造:从存储pair的容器迭代器区间初始化
// 步骤:先创建vector<pair<K,V>>,再用区间构造unordered_map
vector<pair<int, string>> vec = {{1, "one"}, {2, "two"}, {2, "two2"}, {3, "three"}};
unordered_map<int, string> um2(vec.begin(), vec.end());
// 特性:key自动去重(重复key=2仅保留第一个),遍历无序,value随第一个key存储
cout << "um2(迭代器构造)元素:";
for (auto& e : um2) cout << "{" << e.first << "," << e.second << "} "; // 示例:{1,one} {2,two} {3,three}
cout << endl;
// 3. 初始化列表构造(C++11+):最常用,直接传{}包裹的pair/{}键值对
// 写法1:用pair<K,V>
unordered_map<int, string> um3 = {pair<int, string>(1, "张三"), pair<int, string>(2, "李四")};
// 写法2:用make_pair(更简洁)
unordered_map<int, string> um4 = {make_pair(3, "王五"), make_pair(4, "赵六")};
// 写法3:直接{}(C++11+语法糖,嘎嘎好使)
unordered_map<int, string> um5 = {{5, "孙七"}, {6, "周八"}, {6, "周八8"}}; // key=6去重
cout << "um5(初始化列表构造)元素:";
for (auto& e : um5) cout << "{" << e.first << "," << e.second << "} "; // {5,孙七} {6,周八}(无序)
cout << endl;
// 4. 拷贝构造:复制已有unordered_map的所有键值对
unordered_map<int, string> um6(um5);
cout << "um6(拷贝构造)元素个数:" << um6.size() << endl; // 输出2
// 5. 移动构造(C++11+):接管已有unordered_map的资源,原对象变空
// 语法:std::move转左值为右值,避免拷贝,效率更高
unordered_map<int, string> um7(move(um5));
cout << "移动构造后,原um5是否为空:" << um5.empty() << endl; // 输出true
cout << "um7(移动构造)元素:";
for (auto& e : um7) cout << "{" << e.first << "," << e.second << "} "; // 原um5的元素
cout << endl;
// 6. 自定义桶数构造:指定初始桶数(减少后续rehash次数,提升性能)
// 语法:unordered_map<K,V> um(n),n为初始桶数
unordered_map<string, int> um8(10); // 初始桶数10,key=string(STL默认支持哈希)
um8.insert({"apple", 5});
cout << "um8(指定桶数构造)的桶数量:" << um8.bucket_count() << endl; // 输出10(未触发rehash)
/************************ 二、插入操作:insert/[] ************************/
cout << "\n===== 2. 插入操作(insert/[])示例 =====" << endl;
unordered_map<int, string> insert_um;
// ===== 核心差异:unordered_map插入分2类方式 =====
// 方式1:insert接口(与unordered_set语法一致,仅插入pair)
// 1.1 单元素插入:insert(pair<K,V>),返回值为pair<iterator, bool>
// 返回值解析:
// - pair.first:指向插入/已存在key的迭代器(迭代器指向pair<K,V>)
// - pair.second:bool值,true=插入成功(key不存在),false=插入失败(key已存在)
auto ret1 = insert_um.insert(pair<int, string>(10, "十")); // 插入新key=10
cout << "insert(10,十)是否成功:" << ret1.second
<< ",对应值:" << ret1.first->second << endl; // true 十
auto ret2 = insert_um.insert(make_pair(10, "十十")); // 插入重复key=10
cout << "insert(10,十十)是否成功:" << ret2.second
<< ",原value保留:" << ret2.first->second << endl; // false 十(value不覆盖)
// 1.2 指定迭代器插入:insert(hint, pair)
// 参数:hint=迭代器(哈希表忽略,仅提示),pair=键值对
// 返回值:指向插入/已存在key的迭代器(无bool值)
auto it_hint = insert_um.insert(insert_um.begin(), make_pair(20, "二十"));
cout << "指定迭代器插入20,返回迭代器指向value:" << it_hint->second << endl; // 二十
// 1.3 区间插入:insert(first, last)
// 作用:插入[first, last)区间内的所有pair,key去重,无返回值
vector<pair<int, string>> insert_vec = {{30, "三十"}, {40, "四十"}, {20, "二十2"}}; // key=20重复
insert_um.insert(insert_vec.begin(), insert_vec.end());
cout << "区间插入后,insert_um元素:";
for (auto& e : insert_um) cout << "{" << e.first << "," << e.second << "} "; // {10,十} {20,二十} {30,三十} {40,四十}
cout << endl;
// 1.4 初始化列表插入:insert({{k1,v1}, {k2,v2}, ...})
insert_um.insert({{50, "五十"}, {60, "六十"}, {30, "三十3"}}); // key=30重复
cout << "初始化列表插入后,insert_um元素:";
for (auto& e : insert_um) cout << "{" << e.first << "," << e.second << "} "; // 新增50、60,30保留原值
cout << endl;
// 方式2:[]运算符(unordered_map特有!重点!)
// 语法:um[key] = value
// 核心逻辑:
// - 若key不存在:自动插入<key, value>,返回value的引用(可修改)
// - 若key已存在:直接修改对应value,无返回值(覆盖原值)
insert_um[70] = "七十"; // key=70不存在,插入
insert_um[10] = "十号"; // key=10已存在,覆盖value
cout << "[]插入70+修改10后,insert_um元素:";
for (auto& e : insert_um) cout << "{" << e.first << "," << e.second << "} "; // 10的value变为"十号"
cout << endl;
/************************ 三、访问/修改value:[]/at ************************/
cout << "\n===== 3. 访问/修改value([]/at)示例 =====" << endl;
unordered_map<string, int> access_um = {{"apple", 5}, {"banana", 3}, {"orange", 4}};
// 1. []运算符:访问+修改(无key则插入默认值)
cout << "access_um[\"apple\"] = " << access_um["apple"] << endl; // 5
access_um["apple"] = 10; // 修改value
cout << "修改后access_um[\"apple\"] = " << access_um["apple"] << endl; // 10
cout << "access_um[\"grape\"] = " << access_um["grape"] << endl; // 0(key不存在,插入<grape, 0>)
cout << "插入grape后元素个数:" << access_um.size() << endl; // 4
// 2. at()接口:安全访问(仅访问,无key则抛异常,不插入新元素)
// 优点:避免误插入不存在的key,适合只读场景
try {
cout << "access_um.at(\"banana\") = " << access_um.at("banana") << endl; // 3
access_um.at("banana") = 6; // 修改value
cout << "修改后access_um.at(\"banana\") = " << access_um.at("banana") << endl; // 6
cout << "access_um.at(\"pear\") = " << access_um.at("pear") << endl; // 无key,抛out_of_range异常
} catch (const out_of_range& e) {
cout << "at(\"pear\")异常:" << e.what() << endl; // 捕获异常,程序不崩溃
}
/************************ 四、删除操作:erase接口 ************************/
cout << "\n===== 4. 删除操作(erase)示例 =====" << endl;
unordered_map<int, string> erase_um = {{10, "十"}, {20, "二十"}, {30, "三十"}, {40, "四十"}};
cout << "删除前,erase_um元素:";
for (auto& e : erase_um) cout << "{" << e.first << "," << e.second << "} ";
cout << endl;
// 1. 按迭代器删除:erase(pos)
// 参数:指向要删除pair的迭代器,无返回值
// 注意:迭代器仅支持++,不支持--/随机访问,删除前必须判断有效性
auto it_erase = erase_um.find(20); // 先找到key=20的迭代器
if (it_erase != erase_um.end()) {
erase_um.erase(it_erase);
cout << "按迭代器删除20后,erase_um元素:";
for (auto& e : erase_um) cout << "{" << e.first << "," << e.second << "} "; // 无20
cout << endl;
}
// 2. 按key删除:erase(key)
// 返回值:size_t类型,删除的pair个数(key唯一,故返回0或1)
size_t del_count1 = erase_um.erase(30); // 删除存在的key=30
cout << "按key删除30,删除个数:" << del_count1 << endl; // 1
size_t del_count2 = erase_um.erase(100); // 删除不存在的key=100
cout << "按key删除100,删除个数:" << del_count2 << endl; // 0
// 3. 区间删除:erase(first, last)
// 作用:删除[first, last)区间内的所有pair,无返回值
auto it_begin = erase_um.begin();
auto it_end = erase_um.find(40); // 找到key=40的迭代器作为结束位置
if (it_end != erase_um.end()) {
erase_um.erase(it_begin, it_end);
cout << "区间删除后,erase_um剩余元素:";
for (auto& e : erase_um) cout << "{" << e.first << "," << e.second << "} "; // 仅剩余40
cout << endl;
}
/************************ 五、查找操作:find/count ************************/
cout << "\n===== 5. 查找操作(find/count)示例 =====" << endl;
unordered_map<int, string> find_um = {{10, "十"}, {20, "二十"}, {30, "三十"}};
// 1. find接口:find(key)
// 作用:查找key对应的pair,返回迭代器
// 返回值:找到=指向pair的迭代器;未找到=end()
auto it_find = find_um.find(20);
if (it_find != find_um.end()) {
cout << "find(20)找到:key=" << it_find->first << ", value=" << it_find->second << endl; // 20,二十
} else {
cout << "find(20)未找到" << endl;
}
auto it_find_none = find_um.find(100);
if (it_find_none == find_um.end()) {
cout << "find(100)未找到元素" << endl;
}
// 2. count接口:count(key)
// 作用:统计key对应的pair个数(key唯一,故返回0或1)
// 常用场景:快速判断key是否存在(比find更简洁)
size_t count_10 = find_um.count(10);
cout << "count(10)结果:" << count_10 << " → 说明key存在" << endl; // 1
size_t count_100 = find_um.count(100);
cout << "count(100)结果:" << count_100 << " → 说明key不存在" << endl; // 0
/************************ 六、迭代器遍历 ************************/
cout << "\n===== 6. 迭代器遍历示例 =====" << endl;
unordered_map<string, int> iter_um = {{"apple", 5}, {"banana", 3}, {"orange", 4}, {"grape", 2}};
// 1. 普通迭代器遍历(指向pair<K,V>,仅支持++)
cout << "普通迭代器遍历:";
unordered_map<string, int>::iterator it_iter;
for (it_iter = iter_um.begin(); it_iter != iter_um.end(); ++it_iter) {
// 迭代器->first是key,->second是value
cout << "{" << it_iter->first << "," << it_iter->second << "} "; // 无序输出
}
cout << endl;
// 2. const迭代器遍历:只读,不可修改key/value(key本身不可改,value也只读)
cout << "const迭代器遍历:";
unordered_map<string, int>::const_iterator it_const;
for (it_const = iter_um.cbegin(); it_const != iter_um.cend(); ++it_const) {
cout << "{" << it_const->first << "," << it_const->second << "} ";
}
cout << endl;
// 4. 范围for遍历(最简洁,推荐使用)
cout << "范围for遍历:";
for (const auto& e : iter_um) { // 加const&避免拷贝,提升效率
cout << "{" << e.first << "," << e.second << "} ";
}
cout << endl;
/************************ 七、容量操作:size/empty/max_size ************************/
cout << "\n===== 7. 容量操作示例 =====" << endl;
unordered_map<int, string> capacity_um = {{1, "一"}, {2, "二"}, {3, "三"}};
// 1. size():返回当前键值对个数
cout << "capacity_um元素个数(size):" << capacity_um.size() << endl; // 3
// 2. empty():判断是否为空(size==0),返回bool
cout << "capacity_um是否为空(empty):" << boolalpha << capacity_um.empty() << endl; // false
// 3. max_size():返回理论最大存储数(系统/内存限制)
cout << "capacity_um最大可存储元素数(max_size):" << capacity_um.max_size() << endl; // 大数,如18446744073709551615
// 4. clear():清空所有键值对,size=0,桶结构保留
capacity_um.clear();
cout << "clear后,capacity_um是否为空:" << capacity_um.empty() << endl; // true
cout << "clear后,capacity_um元素个数:" << capacity_um.size() << endl; // 0
/************************ 八、哈希表特有操作(桶操作) ************************/
cout << "\n===== 8. 哈希表桶操作示例(unordered_map特有) =====" << endl;
unordered_map<int, string> bucket_um = {{1, "一"}, {2, "二"}, {3, "三"}, {4, "四"}, {5, "五"}};
// 1. bucket_count():返回当前哈希表的桶数量
cout << "当前桶数量(bucket_count):" << bucket_um.bucket_count() << endl; // 示例:7(编译器默认)
// 2. bucket(key):返回key对应的pair所在的桶编号(从0开始)
int key = 3;
size_t bucket_num = bucket_um.bucket(key);
cout << "key=" << key << "所在桶编号:" << bucket_num << endl; // 示例:3
// 3. bucket_size(n):返回第n个桶中的pair个数
size_t bucket_size_num = bucket_um.bucket_size(bucket_num);
cout << "桶" << bucket_num << "中的元素个数:" << bucket_size_num << endl; // 示例:1(无哈希冲突)
// 4. load_factor():返回当前负载因子(size / bucket_count,浮点数)
// 负载因子:哈希表填充程度,超过max_load_factor会触发rehash(扩容)
float load_fac = bucket_um.load_factor();
cout << "当前负载因子(load_factor):" << load_fac << endl; // 示例:5/7≈0.714
// 5. max_load_factor():返回/设置最大负载因子(默认1.0)
float max_load_fac = bucket_um.max_load_factor();
cout << "默认最大负载因子(max_load_factor):" << max_load_fac << endl; // 1.0
// 设置最大负载因子为0.8(超过则自动rehash,增加桶数)
bucket_um.max_load_factor(0.8f);
cout << "设置后最大负载因子:" << bucket_um.max_load_factor() << endl; // 0.8
// 6. rehash(n):强制设置桶数量为n(n≥当前bucket_count)
bucket_um.rehash(10); // 桶数改为10
cout << "rehash(10)后,桶数量:" << bucket_um.bucket_count() << endl; // 10
// 7. reserve(n):预留空间,确保桶数足够存储n个pair而不触发rehash
// 区别于rehash:reserve根据负载因子计算桶数(n/负载因子),rehash直接指定桶数
bucket_um.reserve(8);
cout << "reserve(8)后,桶数量:" << bucket_um.bucket_count() << endl; // 示例:10(8/0.8=10,无需扩容)
/************************ 九、其他操作:swap/修改value ************************/
cout << "\n===== 9. 其他操作(swap/修改value)示例 =====" << endl;
// 1. swap操作:交换两个unordered_map的内容,时间复杂度O(1)(仅交换底层指针)
unordered_map<int, string> swap_um1 = {{1, "一"}, {2, "二"}};
unordered_map<int, string> swap_um2 = {{10, "十"}, {20, "二十"}, {30, "三十"}};
cout << "交换前:" << endl;
cout << "swap_um1元素:";
for (auto& e : swap_um1) cout << "{" << e.first << "," << e.second << "} "; // {1,一} {2,二}
cout << endl;
cout << "swap_um2元素:";
for (auto& e : swap_um2) cout << "{" << e.first << "," << e.second << "} "; // {10,十} {20,二十} {30,三十}
cout << endl;
swap_um1.swap(swap_um2);
cout << "交换后:" << endl;
cout << "swap_um1元素:";
for (auto& e : swap_um1) cout << "{" << e.first << "," << e.second << "} "; // {10,十} {20,二十} {30,三十}
cout << endl;
cout << "swap_um2元素:";
for (auto& e : swap_um2) cout << "{" << e.first << "," << e.second << "} "; // {1,一} {2,二}
cout << endl;
// 2. 修改value(key不可修改,value可任意修改)
unordered_map<string, int> modify_um = {{ "math", 90 }, { "chinese", 85 }};
cout << "修改前:math=" << modify_um["math"] << ", chinese=" << modify_um["chinese"] << endl;
modify_um["math"] = 95; // 直接修改
auto it_modify = modify_um.find("chinese");
if (it_modify != modify_um.end()) {
it_modify->second = 92; // 通过迭代器修改
}
cout << "修改后:math=" << modify_um["math"] << ", chinese=" << modify_um["chinese"] << endl; // 95,92
return 0;
}
// 补充说明:unordered_multimap使用方法与unordered_map基本一致,唯一区别是key可重复!
// 1. insert插入重复key会成功(无返回bool);
// 2. count(key)可返回大于1的数;
// 3. erase(key)会删除所有对应key的pair;
// 其余接口(构造、遍历、桶操作等)完全相同,此处不再赘述!
结语:以哈希之力,驭键值之巧
当你看到这里,相信已经完整掌握了C++ STL中unordered_map容器的核心用法与底层逻辑。从开篇对"高频增删查场景性能优化"的需求引入,到核心特性的精准剖析,再到构造、迭代、插入、删除、查找、桶操作等接口的逐一枚举,最后通过与map的差异对比和完整实操示例落地,我们一步步揭开了这个"哈希驱动的键值映射利器"的神秘面纱。此刻,不妨停下脚步,回顾这段学习旅程,你会发现,unordered_map的学习不仅是对一个容器的掌握,更是对"数据结构决定性能"这一核心编程思想的深刻体悟。
回顾整个学习过程,我们始终围绕"实用"与"理解"两个核心展开。实用层面,我们详细梳理了每一个接口的函数形式、功能说明、适用场景与注意点,尤其是对\[\]运算符与insert/find的区别、桶操作的性能优化价值等关键细节的拆解,都是为了让你在实际开发中能够精准选型、规避陷阱。比如,当你需要快速访问并修改键值对时,\[\]运算符的便捷性无可替代,但你必须牢记它"不存在则自动插入"的特性,避免意外增加无效键值;当你需要批量插入大量数据时,提前通过reserve或rehash预留桶空间,就能有效减少扩容带来的性能损耗------这些细节,正是区分新手与老手的关键。
理解层面,我们始终紧扣"哈希表"这一底层结构,所有特性与接口设计都能从中找到根源。为什么unordered_map遍历无序?因为元素按哈希值存储,而非键的大小排序;为什么它的增删查平均效率是O(1)?因为哈希表能通过键的哈希值直接定位存储位置,无需像红黑树那样进行遍历比较;为什么它不支持反向迭代器?因为哈希表的链式/树式冲突解决结构不具备反向遍历的天然条件。当你将接口用法与底层结构绑定理解时,就不会再死记硬背,而是能通过逻辑推导轻松掌握所有用法------这正是我们学习STL容器的核心方法:知其然,更知其所以然。
在学习unordered_map的过程中,我们不可避免地会将它与之前学习的map进行对比,而这种对比恰恰是掌握两者适用场景的关键。没有绝对优秀的容器,只有最适配场景的选择:当你需要高频增删查、无需有序遍历,且键为基础类型时,unordered_map的O(1)平均效率会成为你的不二之选;当你需要有序遍历、范围查询,或键为自定义类型且易实现小于比较时,map的稳定O(logN)效率和有序特性则更具优势。这种"根据场景选型"的思维,比单纯掌握一个容器的用法更为重要,它会伴随你整个编程生涯,帮助你写出更高效、更优雅的代码。
还记得我们在讲解自定义类型作为键时,需要手动实现哈希函数和等于比较函数吗?那部分内容可能让你感到些许复杂,但这正是编程能力提升的突破口。编程不仅是调用现成的接口,更是理解接口背后的原理,并根据实际需求进行定制化开发。当你能够独立为自定义类型设计合理的哈希函数------保证相同键的哈希值相同、不同键的哈希值尽量不同,你对哈希表的理解就已经超越了基础层面。而这种"拆解需求、定制实现"的能力,正是从"会用工具"到"驾驭工具"的核心跨越。
对于unordered_multimap的补充说明,其实是对"键唯一性"这一核心特性的延伸思考。当业务场景需要一个键对应多个值时,unordered_multimap就能精准满足需求,而它与unordered_map的接口差异------无\[\]运算符、插入返回值无bool标志、删除会移除所有相同键等,都源于"键可重复"这一核心变化。这也提醒我们:学习STL容器时,要抓住每个容器的核心特性,以此为出发点推导接口设计,就能举一反三、触类旁通。
最后,我们提供的完整实操示例,将所有知识点串联起来,从构造函数到各种操作的组合使用,再到异常处理,模拟了实际开发中的常见场景。建议你务必亲手运行这段代码,修改参数、调试细节,在实践中巩固所学知识。编程是一门实践性极强的学科,只有将理论知识转化为实际操作能力,才能真正掌握它。可能在实践过程中,你会遇到哈希冲突导致性能下降、自定义哈希函数设计不合理导致查找失败等问题,但这些问题都是成长的契机,解决它们的过程,正是你编程能力快速提升的过程。
学习STL容器的旅程,就像在搭建一座大厦,每一个容器都是一块坚实的基石。unordered_map作为哈希表思想的典型实现,为我们打开了"高效键值映射"的大门,它的出现让我们在处理高频增删查场景时有了更优的选择。但这绝不是终点,STL中还有许多优秀的容器和算法等待你去探索,比如后续我们可能会学习的priority_queue、bitset等,它们各自有着独特的应用场景和底层逻辑。
或许在学习过程中,你会感到枯燥或困惑,会因为记不住接口用法而沮丧,会因为不理解底层原理而迷茫。但请记住,每一位优秀的程序员都经历过这个阶段。学习编程就像攀登一座高山,过程中难免会遇到陡坡和荆棘,但只要你坚持下去,一步一个脚印地夯实基础,不断实践、不断思考,就一定能登上山顶,领略"会当凌绝顶,一览众山小"的畅快。
希望通过本文的学习,你不仅能够熟练运用unordered_map解决实际问题,更能掌握"理解底层原理、根据场景选型、注重实践验证"的学习方法。在未来的编程道路上,愿你能够以哈希之力,驭键值之巧,用简洁高效的代码解决复杂的业务需求;愿你始终保持对技术的热爱与好奇,不断探索、不断进步,在编程的世界里书写属于自己的精彩。加油,每一位追梦的程序员!