对于C++中unordered_map的详细介绍

开篇介绍:

hello 大家,那么在本篇博客中,我们将学习C++中unordered_map的使用方法,前面我们学习了unordered_set的用法,那么其实unordered_map的用法也差不多,但是依然值得我们去了解一下其各个接口的用处。

unordered_map - C++ Referencehttps://legacy.cplusplus.com/reference/unordered_map/unordered_map/?kw=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 的区别

  1. 与 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"

  2. 与 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),接口用法基本一致,仅以下几点差异:

  1. 插入:单个插入返回 iterator(无 bool 值,因 Key 可重复,插入必然成功);

  2. 删除:按 Key 删除时,会删除所有对应 Key 的键值对,返回实际删除个数(≥1);

  3. 查找:find() 返回中序遍历第一个匹配 Key 的迭代器;count() 返回 Key 的重复次数;equal_range() 是获取所有重复 Key 键值对的最优方式(返回 pair<iterator, iterator>,表示重复 Key 的范围);

  4. 无 \[\] 运算符:因 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解决实际问题,更能掌握"理解底层原理、根据场景选型、注重实践验证"的学习方法。在未来的编程道路上,愿你能够以哈希之力,驭键值之巧,用简洁高效的代码解决复杂的业务需求;愿你始终保持对技术的热爱与好奇,不断探索、不断进步,在编程的世界里书写属于自己的精彩。加油,每一位追梦的程序员!

相关推荐
东东最爱敲键盘1 小时前
day3.c++函数与预处理 string
c++·算法
七夜zippoe1 小时前
基于 DolphinDB 构建全流程质量追溯体系:从原材料到成品的全链路追踪
大数据·人工智能·算法·全链路·dolphindb·质量追溯
rannn_1112 小时前
【力扣hot100】238、41、73题解
java·算法·leetcode·开发
QN1幻化引擎2 小时前
认知场的涌现动力学:结构证明、Phi度量与意识签名电池
人工智能·深度学习·神经网络·算法·机器学习·agi
lingran__2 小时前
C++ 高阶数据结构:AVL 树万字详解|原理、四种旋转、key‑value 实现、调试校验、set/map/红黑树 底层前置知识
数据结构·c++·二叉搜索树·平衡二叉树·avl树·红黑树前置
程序员爱钓鱼2 小时前
Rust 可变借用详解:&mut T 与安全修改数据
后端·面试·rust
程序员爱钓鱼2 小时前
Go 类型转换详解
后端·面试·go
爱和冰阔落2 小时前
【Linux】两个毫无关系的进程怎么通信?命名管道 FIFO 从原理到 Server/Client 实战
android·linux·数据库·c++·vim
疯狂打码的少年3 小时前
【数据结构】顺序表 vs 链表的对比与选择
数据结构·笔记·链表