C++ STL | unordered系列容器
目录
unordered系列关联式容器
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到O( log₂N),即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,分别是unordered_set、unordered_map、unordered_multiset、unordered_multimap这四个容器,与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,因此本文中只对unordered_map进行介绍。
unodered_map
介绍
cplusplus中关于unordered_map的介绍:unordered_map - C++ Reference

其模板参数:
- Key是键的类型T是值的类型
- Hash是哈希函数,默认为std::hash<Key>
- Pred是键相等判断函数,默认为==;
- Alloc是内存分配器
特性:
- 无序性:构造后元素按哈希值分布在不同桶中,遍历顺序不固定;
- 桶数量(bucket count):构造时可指定初始桶数,影响哈希冲突概率(桶数越多,冲突概率越低);
- 自定义规则:支持自定义哈希函数和键相等判断函数,适配非内置类型(如自定义类)。
总而言之:
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_maps实现了直接访问操作符operator[],它允许使用key作为参数直接访问value。
- 它的迭代器至少是前向迭代器,即只支持++、==、!=。
构造函数
默认构造函数:空容器
unordered_map() noexcept;
explicit unordered_map( const Allocator& alloc ); // 指定分配器
创建一个空的 unordered_map,默认桶数量由实现决定,通常为 8 或 16,使用默认哈希 / 比较函数
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 方式1:默认构造空容器
unordered_map<int, string> umap1;
cout << "umap1 大小:" << umap1.size() << endl; // 0
cout << "umap1 初始桶数:" << umap1.bucket_count() << endl; // 通常为 8
// 方式2:指定内存分配器(极少用,默认即可)
allocator<pair<const int, string>> alloc;
unordered_map<int, string> umap2(alloc);
return 0;
}
范围构造函数:从迭代器范围初始化
template <class InputIt>
unordered_map( InputIt first, InputIt last,
size_type bucket_count = /*默认值*/,
const Hash& hash = Hash(),
const KeyEqual& equal = KeyEqual(),
const Allocator& alloc = Allocator() );
从迭代器范围 [first, last) 中拷贝元素初始化容器,可指定初始桶数、哈希函数、比较函数。
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 从数组初始化
pair<int, string> arr[] = {{1, "apple"}, {2, "banana"}, {3, "orange"}};
unordered_map<int, string> umap(arr, arr + 3);
// 从其他容器(如 map)初始化
map<int, string> mp = {{4, "pear"}, {5, "grape"}};
unordered_map<int, string> umap2(mp.begin(), mp.end(), 10); // 指定初始桶数为 10
// 遍历验证
for (auto& p : umap) {
cout << p.first << ":" << p.second << " ";
}
return 0;
}
拷贝构造函数:复制已有容器
unordered_map( const unordered_map& other );
unordered_map( const unordered_map& other, const Allocator& alloc );
创建一个新容器,拷贝 other 的所有元素、哈希函数、比较函数、桶数量等属性。
注意:拷贝构造是深拷贝,新容器和原容器互不影响,即修改 umap2 不会改变 umap1。
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<int, string> umap1 = {{1, "a"}, {2, "b"}};
// 拷贝构造
unordered_map<int, string> umap2(umap1);
cout << "umap2 大小:" << umap2.size() << endl; // 2
cout << "umap2[1]:" << umap2[1] << endl; // a
return 0;
}
移动构造函数:转移已有容器资源
unordered_map( unordered_map&& other ) noexcept;
unordered_map( unordered_map&& other, const Allocator& alloc );
“窃取” other 的资源,例如元素、桶结构、哈希表等,原容器会变为空。
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<int, string> umap1 = {{1, "a"}, {2, "b"}};
// 移动构造(注意 && 右值引用)
unordered_map<int, string> umap2(move(umap1));
cout << "umap2 大小:" << umap2.size() << endl; // 2
cout << "umap1 大小:" << umap1.size() << endl; // 0(原容器为空)
return 0;
}
初始化列表构造(C++11及以上)
unordered_map( initializer_list<pair<const Key, T>> ilist,
size_type bucket_count = /*默认值*/,
const Hash& hash = Hash(),
const KeyEqual& equal = KeyEqual(),
const Allocator& alloc = Allocator() );
通过 {} 初始化列表直接指定键值对,是 C++11 后最常用的初始化方式。
int main() {
// 极简初始化(推荐)
unordered_map<int, string> umap = {
{1, "apple"},
{2, "banana"},
{3, "orange"}
};
// 指定桶数 + 初始化列表
unordered_map<int, string> umap2(
{{4, "pear"}, {5, "grape"}},
20 // 初始桶数 20
);
return 0;
}
自定义哈希 / 比较函数的构造
// 结合桶数、哈希函数、比较函数构造
unordered_map( size_type bucket_count,
const Hash& hash = Hash(),
const KeyEqual& equal = KeyEqual(),
const Allocator& alloc = Allocator() );
适配非内置类型,如自定义类、string 自定义比较,需提供自定义哈希函数或相等判断函数。
// 自定义类
struct Person {
string name;
int age;
// 相等判断:name + age 相同则视为同一键
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 自定义哈希函数
struct PersonHash {
size_t operator()(const Person& p) const {
// 组合 name 和 age 的哈希值
return hash<string>()(p.name) ^ (hash<int>()(p.age) << 1);
}
};
int main() {
// 构造时指定哈希函数和初始桶数
unordered_map<Person, string, PersonHash> umap(
10, // 初始桶数
PersonHash() // 自定义哈希函数
);
// 插入元素
umap.insert({{"Alice", 20}, "student"});
umap.insert({{"Bob", 25}, "engineer"});
// 查找
Person p = {"Alice", 20};
cout << umap[p] << endl; // student
return 0;
}
unordered_map的容量函数
unordered_map 底层是哈希表(桶数组 + 链表 / 红黑树),其容量函数分为两类:
- 通用容量函数:所有容器都有的基础接口(
empty()/size()/max_size()); - 哈希表特有函数:适配哈希结构的性能参数(
bucket_count()/load_factor()等)。
核心概念:
- 桶(bucket):哈希表的存储单元,每个桶对应一个哈希值范围,内部存储哈希冲突的元素;
- 负载因子(load factor):
size() / bucket_count(),表示每个桶平均存储的元素数,超过阈值会触发哈希表扩容。
通用容量函数的语义和 map 完全相同,仅底层实现有差异。
| 函数名 | 语法 | 功能与说明 |
|---|---|---|
empty() | bool empty() const; | 判断容器是否为空(无元素),返回 true/false;时间复杂度:O (1);优先用 empty() 而非 size() == 0(更高效)。 |
size() | size_type size() const; | 返回当前元素个数(键值对数量),类型为 size_t;时间复杂度:O (1)(哈希表维护计数器)。 |
max_size() | size_type max_size() const; | 返回理论最大元素数(受系统内存 / 类型大小限制);仅作参考,实际无法达到。 |
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};
// 1. empty():判断是否为空
cout << "是否为空:" << boolalpha << umap.empty() << endl; // false
// 2. size():元素个数
cout << "元素个数:" << umap.size() << endl; // 3
// 3. max_size():理论最大值(不同系统值不同)
cout << "理论最大容量:" << umap.max_size() << endl; // 示例:128102389400760775
// 清空后验证
umap.clear();
cout << "清空后是否为空:" << umap.empty() << endl; // true
cout << "清空后元素个数:" << umap.size() << endl; // 0
return 0;
}
是否为空:false
元素个数:3
理论最大容量:128102389400760775
清空后是否为空:true
清空后元素个数:0
哈希表特有容量函数是哈希表的核心调优接口,直接影响查找 / 插入效率。
| 函数名 | 语法 | 功能与说明 |
|---|---|---|
bucket_count() | size_type bucket_count() const; | 返回当前桶的数量(哈希表的桶数组大小);初始桶数由实现决定(通常 8/16),扩容时翻倍。 |
max_bucket_count() | size_type max_bucket_count() const; | 返回桶的理论最大数量(受系统内存限制)。 |
load_factor() | float load_factor() const; | 返回当前负载因子:size() / (float)bucket_count();反映哈希冲突程度,值越大冲突越多。 |
max_load_factor() | float max_load_factor() const; void max_load_factor(float z); | ① 无参:返回扩容阈值(默认 1.0);② 有参:设置扩容阈值(如 0.75);当 load_factor() > max_load_factor() 时,哈希表自动扩容(桶数翻倍)。 |
rehash() | void rehash(size_type n); | 强制将桶数调整为 至少 n;若 n ≤ 当前桶数,无操作;若 n 更大,重新分配桶并重新哈希所有元素。 |
reserve() | void reserve(size_type n); | 预分配桶数,使容器能容纳 至少 n 个元素 且不触发扩容;底层计算:桶数 = n /max_load_factor (),再调用 rehash()。 |
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap;
// 初始状态
cout << "初始桶数:" << umap.bucket_count() << endl; // 8(默认)
cout << "初始负载因子:" << umap.load_factor() << endl; // 0.0
cout << "默认最大负载因子:" << umap.max_load_factor() << endl; // 1.0
// 插入元素,触发负载因子变化
umap.insert({{1, "a"}, {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"}, {6, "f"}, {7, "g"}, {8, "h"}});
cout << "\n插入8个元素后:" << endl;
cout << "元素个数:" << umap.size() << endl; // 8
cout << "桶数:" << umap.bucket_count() << endl; // 8
cout << "负载因子:" << umap.load_factor() << endl; // 1.0(达到阈值)
// 插入第9个元素,触发扩容(桶数翻倍)
umap[9] = "i";
cout << "\n插入第9个元素后:" << endl;
cout << "桶数:" << umap.bucket_count() << endl; // 16(扩容)
cout << "负载因子:" << umap.load_factor() << endl; // 0.5625(9/16)
// 手动调整最大负载因子
umap.max_load_factor(0.75);
cout << "\n设置最大负载因子为0.75后:" << endl;
cout << "当前最大负载因子:" << umap.max_load_factor() << endl; // 0.75
// 预分配桶数(reserve)
umap.reserve(100); // 预分配足够桶数,容纳100个元素不扩容
cout << "\nreserve(100) 后桶数:" << umap.bucket_count() << endl; // 134(100/0.75 ≈ 133.33,向上取整)
// 强制调整桶数(rehash)
umap.rehash(200);
cout << "rehash(200) 后桶数:" << umap.bucket_count() << endl; // 200
return 0;
}
初始桶数:8
初始负载因子:0
默认最大负载因子:1
插入8个元素后:
元素个数:8
桶数:8
负载因子:1
插入第9个元素后:
桶数:16
负载因子:0.5625
设置最大负载因子为0.75后:
当前最大负载因子:0.75
reserve(100) 后桶数:134
rehash(200) 后桶数:200
unordered_map的迭代器

同map一样,unordered_map的迭代器只支持单向迭代
- 支持操作:单向前进(
++it/it++)、解引用(*it)、箭头访问(it->first/second)、相等 / 不等比较(==/!=)、保存状态多次遍历; - 禁止操作:反向遍历(
--it/it--,标准不强制支持,部分编译器为扩展特性)、随机访问(it+n/it-n/it[])、大小比较(</>/<=/>=)。
注意的是,某些编译器可能会对unordered_map的迭代器实现反向遍历的扩展功能,在编写可移植代码时,若使用了反向遍历,可能在跨平台时报错。
遍历时无序性,与插入 / 键序无关:
- 元素按哈希值散列到不同桶中,迭代器先按桶的顺序遍历,桶内按元素插入顺序遍历;
- 遍历结果既不遵循插入顺序,也不按键的大小升 / 降序排列,且扩容后遍历顺序可能发生变化(桶数组重新分配,元素重新散列);
- 相同键值对在不同编译器 / 系统下的遍历顺序可能不同,业务逻辑切勿依赖遍历顺序。
读写规则,键只读、值可写:
- 键(
first)为const:禁止修改,若修改键会导致其哈希值变化,元素会从哈希表中 “丢失”,无法通过原键 / 新键查找,编译器会对修改键的操作直接报错; - 值(
second)为普通类型:支持直接修改,修改值不会改变键的哈希值,不影响哈希表的结构和查找逻辑。
存储特性,逻辑连续、物理离散:
迭代器遍历是逻辑上的连续遍历—— 从 begin() 开始,通过 ++it 可依次访问所有元素,直到 end()(尾后迭代器),无需关心元素所属的桶;但元素的物理内存是离散的—— 不同桶的元素分布在内存不同位置,桶内元素也为链式存储,这也是迭代器不支持随机访问的根本原因(无法通过内存偏移快速定位元素)。
unordered_map 提供 4 种迭代器类型,按遍历范围(全局 / 桶内) 和读写权限(读写 / 只读) 划分,覆盖全容器遍历、哈希冲突处理等所有场景。
| 迭代器类型 | 关键字 | 遍历范围 | 读写权限 | 核心场景 | 获取方式 |
|---|---|---|---|---|---|
| 普通全局迭代器 | iterator | 整个容器所有元素 | 可读可写 | 常规遍历、修改值、遍历删除 | begin()/end()/find() |
| 常量全局迭代器 | const_iterator | 整个容器所有元素 | 只读 | 只读遍历、const unordered_map 遍历 | cbegin()/cend()/find()(const 对象) |
| 普通桶内迭代器 | local_iterator | 单个桶内所有元素 | 可读可写 | 处理哈希冲突、统计桶内元素 | begin(n)/end(n)(n 为桶索引) |
| 常量桶内迭代器 | const_local_iterator | 单个桶内所有元素 | 只读 | 只读处理哈希冲突、const 对象桶内遍历 | cbegin(n)/cend(n)(n 为桶索引) |
全局迭代器(iterator/const_iterator)
最常用的迭代器类型,负责遍历整个 unordered_map 的所有元素,所有对全容器的常规操作均依赖该类型。const_iterator 是只读版本,适配 const unordered_map 或无需修改值的场景,可避免数据被意外修改。
正向遍历:
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}};
// 写法1:C++11 范围for循环(推荐,语法最简,自动使用iterator)
cout << "范围for遍历(可修改值):" << endl;
for (auto& p : umap) { // & 避免拷贝,支持修改值;只读场景用 const auto&
p.second += "_fruit"; // 合法:修改值
cout << p.first << ":" << p.second << " ";
}
cout << endl;
// 写法2:auto简化全局迭代器(兼顾简洁与可控,可指定遍历范围)
cout << "auto全局迭代器遍历:" << endl;
for (auto it = umap.begin(); it != umap.end(); ++it) {
cout << it->first << ":" << it->second << " ";
}
cout << endl;
// 写法3:常量全局迭代器遍历(只读,适配const unordered_map)
const unordered_map<int, string> const_umap = umap;
cout << "常量全局迭代器遍历:" << endl;
for (auto cit = const_umap.cbegin(); cit != const_umap.cend(); ++cit) {
cout << cit->first << ":" << cit->second << " ";
}
cout << endl;
return 0;
}
范围for遍历(可修改值):
1:apple_fruit 2:banana_fruit 3:orange_fruit 4:pear_fruit
auto全局迭代器遍历:
1:apple_fruit 2:banana_fruit 3:orange_fruit 4:pear_fruit
常量全局迭代器遍历:
1:apple 2:banana 3:orange 4:pear
桶内迭代器(local_iterator/const_local_iterator)
unordered_map 专属迭代器,专门用于遍历单个桶内的所有元素,核心解决哈希冲突场景的精细操作(如统计桶内冲突元素数量、遍历冲突元素、调试哈希冲突等)。
- 桶索引通过
bucket(Key k)获取:返回键k所属的桶的索引(整数); - 桶内迭代器通过带参的
begin(n)/end(n)获取:n为桶索引,返回第n个桶的起始 / 尾后迭代器; - 桶内迭代器同样为前向迭代器,仅支持在当前桶内单向前进。
桶内迭代器,处理哈希冲突:
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {10, "orange"}, {19, "pear"}};
// 假设1、10、19哈希冲突,均散列到桶0
// 1. 获取键1所属的桶索引
int bucket_idx = umap.bucket(1);
cout << "键1所属桶索引:" << bucket_idx << endl;
cout << "该桶内元素数量:" << umap.bucket_size(bucket_idx) << endl;
// 2. 用普通桶内迭代器遍历桶内元素(可修改值)
cout << "桶内元素遍历(可修改):" << endl;
for (unordered_map<int, string>::local_iterator lit = umap.begin(bucket_idx); lit != umap.end(bucket_idx); ++lit) {
lit->second += "_conflict";
cout << lit->first << ":" << lit->second << " ";
}
cout << endl;
// 3. 用常量桶内迭代器遍历桶内元素(只读)
cout << "桶内元素遍历(只读):" << endl;
for (auto clit = umap.cbegin(bucket_idx); clit != umap.cend(bucket_idx); ++clit) {
cout << clit->first << ":" << clit->second << " ";
}
return 0;
}
键1所属桶索引:0
该桶内元素数量:3
桶内元素遍历(可修改):
1:apple_conflict 10:orange_conflict 19:pear_conflict
桶内元素遍历(只读):
1:apple_conflict 10:orange_conflict 19:pear_conflict
迭代器失效
unordered_map 迭代器的有效性与哈希表的扩容(rehash)强相关,这是其与 map 迭代器的最大区别(map 基于红黑树,几乎不导致迭代器失效)。所有操作的迭代器有效性规则如下:
| 操作类型 | 迭代器有效性情况 | 备注 |
|---|---|---|
| 插入元素(insert/emplace/[]) | ① 未触发扩容:所有迭代器均有效;② 触发扩容:所有迭代器失效(桶数组重新分配) | end() 迭代器始终失效 |
| 删除单个元素(erase (it)) | 仅被删除的迭代器it失效,其余所有迭代器均有效 | 与 map 完全一致 |
| 批量删除(erase (first, last)) | 仅[first, last)范围内的迭代器失效,其余所有迭代器均有效 | 与 map 完全一致 |
| 强制扩容 / 调整桶数(rehash ()/reserve ()) | 所有迭代器均失效(桶数组重新分配,元素重新散列) | 哈希表核心操作,直接失效 |
| 清空容器(clear ()) | 所有迭代器均失效 | 与 map 完全一致 |
| 修改值(it->second = ...) | 所有迭代器均有效 | 不影响哈希表结构 |
| 遍历桶内元素(local_iterator) | 未修改该桶元素时,桶内迭代器始终有效;删除桶内元素时,仅被删迭代器失效 | 局部有效性,更安全 |
迭代器遍历过程中删除元素的坑
同map的迭代器一样,遍历中删除元素的关键是避免迭代器失效,unordered_map 支持 C++11 起的通用安全写法:利用 erase(it) 的返回值(指向被删除元素的下一个有效迭代器)更新迭代器,无需提前记录下一个迭代器。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}, {5, "grape"}};
// 遍历删除键为偶数的元素(安全写法)
for (auto it = umap.begin(); it != umap.end();) { // 注意:无++it,由erase返回值控制
if (it->first % 2 == 0) {
it = umap.erase(it); // 核心:用返回值更新迭代器,避免失效
} else {
++it; // 未删除时,正常单向前进
}
}
// 验证删除结果
cout << "删除偶数键后遍历:" << endl;
for (auto& p : umap) {
cout << p.first << ":" << p.second << " ";
}
return 0;
}
删除偶数键后遍历:
1:apple 3:orange 5:grape
unordered_map的元素访问
unordered_map提供了3种元素访问方式,分别是operator[]、at() 和 迭代器解引用三种方式
operator[]是最常用的访问方式,支持直接读取或修改值,但存在 “隐式插入” 的特性#include <iostream> #include <unordered_map> using namespace std; int main() { unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}}; // 1. 访问已存在的键 cout << "umap[1] = " << umap[1] << endl; // 输出:apple // 2. 修改已存在的键 umap[1] = "red apple"; cout << "修改后 umap[1] = " << umap[1] << endl; // 输出:red apple // 3. 访问不存在的键(隐式插入) cout << "umap[3] = " << umap[3] << endl; // 输出空字符串,且插入 {3, ""} cout << "插入后大小:" << umap.size() << endl; // 输出:3 return 0; }- at()是 C++11 新增的安全访问接口,仅在键存在时返回值的引用,否则直接抛
std::out_of_range异常,避免了隐式插入的风险。// 延续上面的代码 try { // 访问已存在的键 cout << "umap.at(2) = " << umap.at(2) << endl; // 输出:banana // 修改已存在的键 umap.at(2) = "yellow banana"; cout << "修改后 umap.at(2) = " << umap.at(2) << endl; // 输出:yellow banana // 访问不存在的键(抛出异常) cout << "umap.at(4) = " << umap.at(4) << endl; } catch (const out_of_range& e) { cout << "异常:" << e.what() << endl; // 输出:invalid unordered_map<K,T> key }
unordered_map的查询接口
除了访问元素,unordered_map 还提供了专门的查询接口,用于判断键的存在性或获取元素范围。
find()是最核心的查询接口,返回指向目标键的迭代器,若键不存在则返回end()unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}}; auto it = umap.find(2); if (it != umap.end()) { cout << "找到键2:" << it->second << endl; } else { cout << "键2不存在" << endl; }count()用于判断键是否存在,返回值为size_type0 或 1,因为unordered_map键唯一if (umap.count(3)) { cout << "键3存在" << endl; } else { cout << "键3不存在" << endl; }
unordered_map的修改操作
unordered_map 键具有唯一性,插入时若键已存在,不会覆盖原有值,仅插入失败。若需更新值,需先通过迭代器 /operator[]/at() 定位后修改。
unordered_map提供 4 种核心插入方式,适配单键值对插入、批量插入、高效原地构造等场景,同时支持 C++11 后的移动语义插入,减少拷贝开销。
插入操作
1. insert()
insert() 是最基础、最安全的插入方式,支持单键值对、迭代器范围、初始化列表三种插入形式,键存在时插入失败,返回结果可判断是否插入成功,无任何隐式行为(区别于 operator[])
返回值说明
插入单个键值对时,返回 pair<iterator, bool>:
first:指向目标键的迭代器(无论插入成功 / 失败,均指向该键对应的元素);second:布尔值,true表示插入成功(键不存在),false表示插入失败(键已存在)。
// 形式1:插入单个键值对,返回 pair<iterator, bool>
pair<iterator, bool> insert(const value_type& val);
// 形式2:插入迭代器范围的元素(批量插入)
template <class InputIt> void insert(InputIt first, InputIt last);
// 形式3:插入初始化列表(C++11+,简洁批量插入)
void insert(initializer_list<value_type> ilist);
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}};
pair<unordered_map<int, string>::iterator, bool> ret;
// 1. 插入单个新键值对(成功)
ret = umap.insert({3, "orange"});
if (ret.second) {
cout << "插入成功:" << ret.first->first << ":" << ret.first->second << endl;
} else {
cout << "键3已存在,值为:" << ret.first->second << endl;
}
// 2. 插入已存在的键(失败,不覆盖原有值)
ret = umap.insert({2, "yellow banana"});
if (!ret.second) {
cout << "插入失败:键2已存在,原值为:" << ret.first->second << endl;
}
// 3. 从迭代器范围批量插入(从vector插入)
vector<pair<int, string>> vec = {{4, "pear"}, {5, "grape"}};
umap.insert(vec.begin(), vec.end());
// 4. 初始化列表批量插入
umap.insert({{6, "mango"}, {7, "pineapple"}});
// 遍历验证
cout << "插入后容器:";
for (auto& p : umap) cout << p.first << ":" << p.second << " ";
cout << endl;
return 0;
}
插入成功:3:orange
插入失败:键2已存在,原值为:banana
插入后容器:7:pineapple 6:mango 5:grape 4:pear 3:orange 2:banana 1:apple
2. emplace() / emplace_hint()
emplace() / emplace_hint()是原地构造插入,C++11 新增的原地构造插入方式,直接在哈希表的桶中构造键值对,无需先创建临时对象再拷贝,性能优于 insert()(减少一次拷贝 / 移动),适用于自定义类型或大对象作为值的场景。
// 原地构造键值对,返回值与insert()单个插入一致:pair<iterator, bool>
template <class... Args> pair<iterator, bool> emplace(Args&&... args);
// 带提示的原地构造,hint为建议的插入位置(哈希表中提示无效,仅兼容map接口)
template <class... Args> iterator emplace_hint(iterator hint, Args&&... args);
其中hint 为迭代器提示,在 unordered_map 中无实际作用(哈希表按哈希值确定插入位置,不依赖提示),仅为了与 map 接口兼容,返回值为指向目标键的迭代器(无需判断 bool)。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap;
// 1. emplace 原地构造插入新键值对(成功)
auto ret = umap.emplace(1, "apple");
if (ret.second) {
cout << "emplace插入成功:" << ret.first->first << ":" << ret.first->second << endl;
}
// 2. emplace 插入已存在键(失败,不覆盖)
ret = umap.emplace(1, "green apple");
if (!ret.second) {
cout << "emplace插入失败:键1已存在,原值为:" << ret.first->second << endl;
}
// 3. emplace_hint 带提示插入(hint为begin(),无实际作用)
auto it = umap.emplace_hint(umap.begin(), 2, "banana");
cout << "emplace_hint插入结果:" << it->first << ":" << it->second << endl;
// 遍历验证
cout << "最终容器:";
for (const auto& p : umap) {
cout << p.first << ":" << p.second << " ";
}
cout << endl;
return 0;
}
emplace插入成功:1:apple
emplace插入失败:键1已存在,原值为:apple
emplace_hint插入结果:2:banana
最终容器:2:banana 1:apple
3. operator[]
operator[] 是最便捷的操作接口,兼具插入和更新双重功能,无需单独判断键是否存在:键存在时直接返回值的引用用于修改;键不存在时自动隐式插入 {key, T()}(值为默认构造的空对象),并返回值的引用。核心风险为纯读取时的意外插入,易导致容器数据污染、无故扩容。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}};
// 1. 键存在 → 直接更新值(核心用途)
umap[1] = "red apple";
cout << "更新键1后:" << umap[1] << endl;
// 2. 键不存在 → 隐式插入(值为string默认空字符串)
cout << "读取不存在的键3:" << umap[3] << endl; // 空字符串
cout << "隐式插入后容器大小:" << umap.size() << endl; // 从2变为3
// 3. 直接插入新键值对(便捷写法)
umap[4] = "pear";
cout << "直接插入键4:" << umap[4] << endl;
// 遍历验证(包含键3的空值)
cout << "最终容器:";
for (const auto& p : umap) {
cout << p.first << ":" << p.second << " ";
}
cout << endl;
return 0;
}
更新键1后:red apple
读取不存在的键3:
隐式插入后容器大小:3
直接插入键4:pear
最终容器:4:pear 3: 2:banana 1:red apple
4. at()
at()是C++11 新增的安全更新接口,仅用于更新已存在的键,无任何隐式行为:键存在时返回值的引用可直接修改;键不存在时直接抛出 std::out_of_range 异常,从语法层面保证 “键必须存在” 的严格场景,适合对数据一致性要求高的业务场景。
#include <iostream>
#include <unordered_map>
#include <stdexcept>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};
try {
// 1. 键存在 → 安全更新
umap.at(2) = "yellow banana";
cout << "at()更新键2后:" << umap.at(2) << endl;
// 2. 键不存在 → 抛出std::out_of_range异常
umap.at(4) = "pear";
} catch (const out_of_range& e) {
cout << "at()操作异常:" << e.what() << endl;
}
// 遍历验证(仅键2被更新)
cout << "最终容器:";
for (const auto& p : umap) {
cout << p.first << ":" << p.second << " ";
}
cout << endl;
return 0;
}
删除操作
unordered_map 提供按迭代器、按键、按迭代器范围三种删除方式,所有删除操作均不会触发哈希表扩容(仅减少元素数量,不改变桶结构),且仅被删除的迭代器失效,其余所有迭代器(包括桶内迭代器)均保持有效
// 形式1:按迭代器删除,返回指向被删元素下一个元素的迭代器(C++11+,遍历删除安全写法)
iterator erase(iterator pos);
// 形式2:按键删除,返回被删除的元素个数(0 或 1,因键唯一)
size_type erase(const Key& key);
// 形式3:按迭代器范围删除,返回指向最后一个被删元素下一个的迭代器
iterator erase(iterator first, iterator last);
- 按迭代器删除:返回值为下一个有效迭代器,是遍历中删除元素的唯一安全写法;
- 按键删除:返回值 0 表示键不存在,1 表示删除成功;
- 范围删除:适配容器通用接口,因
unordered_map遍历无序,范围无实际业务意义,仅用于批量删除。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}, {5, "grape"}, {6, "mango"}};
size_t del_count;
unordered_map<int, string>::iterator it;
// 1. 按键删除:存在的键(成功,返回1)
del_count = umap.erase(3);
cout << "按键删除3:删除个数=" << del_count << endl;
// 2. 按键删除:不存在的键(失败,返回0)
del_count = umap.erase(7);
cout << "按键删除7:删除个数=" << del_count << endl;
// 3. 按迭代器删除:单个元素(先find定位,再删除)
it = umap.find(4);
if (it != umap.end()) {
it = umap.erase(it); // 返回下一个有效迭代器(指向5)
cout << "删除4后,下一个元素:" << it->first << ":" << it->second << endl;
}
// 4. 遍历中安全删除(核心写法:利用erase返回值更新迭代器,无单独++it)
cout << "遍历删除偶数键前:";
for (const auto& p : umap) cout << p.first << ":" << p.second << " ";
cout << endl;
for (it = umap.begin(); it != umap.end();) { // 循环条件无++it
if (it->first % 2 == 0) {
it = umap.erase(it); // 删完自动指向下一个,无需++
} else {
++it; // 未删除时正常前进
}
}
// 5. 范围删除:删除[begin(), find(5))之间的元素(删除1)
it = umap.find(5);
umap.erase(umap.begin(), it);
// 遍历验证最终结果
cout << "所有删除操作后,最终容器:";
for (const auto& p : umap) cout << p.first << ":" << p.second << " ";
cout << endl;
return 0;
}
按键删除3:删除个数=1
按键删除7:删除个数=0
删除4后,下一个元素:5:grape
遍历删除偶数键前:6:mango 5:grape 2:banana 1:apple
所有删除操作后,最终容器:5:grape
清空与交换操作
clear() 和 swap() 两个辅助修改接口,分别用于快速清空容器所有元素和高效交换两个容器的全部资源
clear(),仅清空元素,删除容器内所有键值对,容器大小(size())变为 0,不释放哈希表的桶数组内存,桶数(bucket_count())保持不变,后续插入元素可直接复用原有桶结构,减少内存分配和初始化开销
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};
cout << "clear() 前:size=" << umap.size() << ",桶数=" << umap.bucket_count() << endl;
// 清空所有元素
umap.clear();
cout << "clear() 后:size=" << umap.size() << ",桶数=" << umap.bucket_count() << endl;
// 后续插入复用原有桶结构,无需重新分配
umap.insert({4, "pear"});
cout << "复用桶结构插入后:" << umap[4] << ",当前桶数=" << umap.bucket_count() << endl;
return 0;
}
clear() 前:size=3,桶数=8
clear() 后:size=0,桶数=8
复用桶结构插入后:pear,当前桶数=8
swap(),交换两个同类型 unordered_map 的全部资源,包括元素、桶数组、哈希函数、负载因子、大小 / 桶数计数器等。注意的是,交换后,指向原容器的迭代器仍有效,但会指向另一个容器的对应元素;仅两个容器的 end() 迭代器失效
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<int, string> umap1 = {{1, "apple"}, {2, "banana"}};
unordered_map<int, string> umap2 = {{3, "orange"}, {4, "pear"}, {5, "grape"}};
// 保存umap1中键1的迭代器
auto it = umap1.find(1);
cout << "交换前:" << endl;
cout << "umap1:"; for (const auto& p : umap1) cout << p.first << ":" << p.second << " ";
cout << "\numap2:"; for (const auto& p : umap2) cout << p.first << ":" << p.second << " ";
cout << endl;
// 交换两个容器的全部资源
umap1.swap(umap2);
cout << "\n交换后:" << endl;
cout << "umap1:"; for (const auto& p : umap1) cout << p.first << ":" << p.second << " ";
cout << "\numap2:"; for (const auto& p : umap2) cout << p.first << ":" << p.second << " ";
cout << endl;
// 原迭代器仍有效,指向umap2中的对应元素
cout << "\n原umap1的迭代器现在指向:" << it->first << ":" << it->second << endl;
return 0;
}
交换前:
umap1:1:apple 2:banana
umap2:5:grape 4:pear 3:orange
交换后:
umap1:5:grape 4:pear 3:orange
umap2:1:apple 2:banana
原umap1的迭代器现在指向:1:apple
更多推荐
所有评论(0)