C++笔记·-- STL unordered_map

在C++ STL中,unordered_map 是一种**无序关联式容器**,核心功能是存储键值对(key-value),通过键(key)快速查找对应的值(value),其底层基于哈希表实现,查找效率接近O(1),是日常开发中"键值映射"场景的首选容器之一。

本文从基础认知、初始化、核心API、底层原理、迭代器使用、与map的核心区别、常见误区及面试考点,全面整理unordered_map知识点,兼顾基础应用与应试需求,避免冗余,直击重点。

一、unordered_map 基础认知

1. 核心定义

unordered_map 是STL中**无序键值对容器**,键(key)唯一,每个key对应一个value,底层依赖哈希表(哈希桶)实现,不保证键值对的存储顺序,也不支持按键的顺序遍历(与map的有序性形成核心区别)。

核心特点:

键唯一:同一个key只能出现一次,重复插入会覆盖原有value;

无序性:存储顺序与插入顺序无关,取决于key的哈希值;

高效查找:平均查找、插入、删除时间复杂度均为O(1),最坏情况O(n)(哈希冲突严重时);

不支持随机访问:无法通过下标[i]访问第i个元素(仅支持通过key访问value)。

2. 头文件与命名空间

使用unordered_map 必须包含对应头文件,默认在std命名空间下(可通过using namespace std;简化使用):

cpp 复制代码
#include <unordered_map>  // 必须包含的头文件
using namespace std;  // 可选,不写则需写 std::unordered_map

3. 适用场景

适合"通过key快速查找value"的场景,例如:

统计元素出现次数(如统计字符串中每个字符的出现次数);

键值映射(如学生ID对应学生信息、配置项key对应配置值);

快速查找、插入、删除,且不关心元素顺序的场景。

二、unordered_map 的定义与初始化

unordered_map 的初始化方式与其他STL容器类似,支持多种初始化写法,重点掌握前3种常用方式:

cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    // 1. 空容器初始化(最常用)
    unordered_map<string, int> um1;  // key为string类型,value为int类型

    // 2. 列表初始化(C++11及以上支持,简洁直观)
    unordered_map<string, int> um2 = {{"apple", 5}, {"banana", 3}, {"orange", 4}};

    // 3. 拷贝初始化(复制另一个unordered_map)
    unordered_map<string, int> um3(um2);

    // 4. 范围初始化(复制另一个容器的指定范围)
    unordered_map<string, int> um4(um2.begin(), um2.end());

    // 5. 指定哈希桶数量(优化性能,可选)
    unordered_map<string, int> um5(10);  // 初始哈希桶数量为10

    return 0;
}

注意:key的类型必须支持哈希运算(如int、string、char等基础类型),自定义类型需手动实现哈希函数(后续补充)。

三、unordered_map 核心常用API(必掌握)

unordered_map 的API围绕"键值对的插入、删除、查找、遍历"展开,重点掌握以下常用方法,结合代码示例理解,避免死记硬背。

1. 插入键值对(4种常用方式)

cpp 复制代码
unordered_map<string, int> um;

// 方式1:使用insert()插入pair对象(最规范)
um.insert(pair<string, int>("apple", 5));

// 方式2:使用insert()插入make_pair(简洁)
um.insert(make_pair("banana", 3));

// 方式3:使用[]赋值插入(最常用,注意:key不存在则插入,存在则覆盖value)
um["orange"] = 4;  // 插入键值对("orange",4)
um["apple"] = 6;   // 覆盖原有value,此时apple对应的值为6

// 方式4:emplace()原位构造(效率高于insert,C++11及以上)
um.emplace("pear", 2);  // 直接在容器内构造键值对,避免拷贝

关键注意:[] 赋值时,若key不存在,会自动插入该键值对(value为对应类型的默认值,如int默认0);若key存在,会直接覆盖value。

2. 查找键值对(核心API)

cpp 复制代码
unordered_map<string, int> um = {{"apple",5}, {"banana",3}};

// 方式1:使用find()查找(返回迭代器)
auto it = um.find("apple");  // 查找key为"apple"的元素
if (it != um.end()) {
    // 找到:it->first 是key,it->second 是value
    cout << "找到:" << it->first << "=" << it->second << endl;
} else {
    // 未找到:返回um.end()
    cout << "未找到该key" << endl;
}

// 方式2:使用[]查找(简洁,但有副作用)
int val1 = um["apple"];  // 找到,返回对应value(5)
int val2 = um["grape"];  // 未找到,自动插入("grape", 0),返回0

// 方式3:使用count()判断key是否存在(返回0或1,因为key唯一)
if (um.count("banana")) {
    cout << "banana存在" << endl;
} else {
    cout << "banana不存在" << endl;
}

重点:find() 是最安全的查找方式,不会修改容器;[] 查找有副作用(未找到会插入),需谨慎使用;count() 仅判断存在性,不返回value。

3. 删除键值对

cpp 复制代码
unordered_map<string, int> um = {{"apple",5}, {"banana",3}, {"orange",4}};

// 方式1:通过key删除(最常用)
um.erase("apple");  // 删除key为"apple"的键值对

// 方式2:通过迭代器删除
auto it = um.find("banana");
if (it != um.end()) {
    um.erase(it);  // 删除迭代器指向的键值对
}

// 方式3:删除所有元素(清空容器)
um.clear();

// 方式4:删除指定范围(迭代器范围)
um.erase(um.begin(), um.end());  // 等价于clear()

4. 容量与大小操作

方法 作用 示例
size() 获取容器中键值对的个数 um.size() → 返回当前元素个数
empty() 判断容器是否为空(空返回true) if (um.empty()) { ... }
bucket_count() 获取哈希桶的数量(底层相关) um.bucket_count() → 查看当前哈希桶个数

5. 其他常用API

um.swap(um2):交换两个unordered_map的内容;

um.max_size():获取容器最大可存储的键值对个数(系统限制);

um.at(key):通过key访问value,若key不存在,抛异常(比[]安全)。

四、unordered_map 迭代器(结合之前所学迭代器知识)

结合STL迭代器的基础,unordered_map 的迭代器属于**双向迭代器**(与list、map一致),不支持随机访问(不能用it + n[]),仅支持++it--it移动。

1. 迭代器类型

unordered_map<K, V>::iterator:非const迭代器,可读写键值对(key不可修改,value可修改);

unordered_map<K, V>::const_iterator:const迭代器,只读,不可修改键值对;

unordered_map<K, V>::reverse_iterator:反向迭代器,从容器末尾开始遍历。

关键注意:unordered_map 的key是不可修改的(因为key的哈希值决定了其存储位置,修改key会导致哈希冲突),因此即使是普通迭代器,也不能修改it->first(key),只能修改it->second(value)。

2. 迭代器遍历(常用写法)

cpp 复制代码
unordered_map<string, int> um = {{"apple",5}, {"banana",3}, {"orange",4}};

// 方式1:普通迭代器遍历(可修改value)
for (unordered_map<string, int>::iterator it = um.begin(); it != um.end(); ++it) {
    cout << it->first << "=" << it->second << " ";
    it->second += 1;  // 可修改value
}

// 方式2:C++11 auto简化(最常用)
for (auto it = um.begin(); it != um.end(); ++it) {
    cout << it->first << "=" << it->second << " ";
}

// 方式3:const迭代器(只读,不可修改)
for (auto it = um.cbegin(); it != um.cend(); ++it) {
    cout << it->first << "=" << it->second << " ";
    // it->second = 10;  // 报错,只读迭代器
}

// 方式4:范围for遍历(C++11及以上,最简洁)
for (auto& pair : um) {  // 用引用避免拷贝,提高效率
    cout << pair.first << "=" << pair.second << " ";
}

五、unordered_map 底层原理(面试核心)

unordered_map 的核心优势的是"高效查找",其底层依赖**哈希表(哈希桶模型)** 实现,这也是它与map(红黑树实现)的核心区别。

1. 底层结构:哈希桶模型

底层由两部分组成:

哈希桶数组(vector实现):存储多个"桶",每个桶对应一个链表(或红黑树,C++11后优化);

链表/红黑树:每个桶中存储"哈希值相同的键值对"(解决哈希冲突)。

核心流程:

  1. 插入键值对时,通过key的哈希函数,计算出对应的哈希值;

  2. 通过哈希值取模(对哈希桶数组大小取模),确定该键值对要放入的"桶";

  3. 若该桶中没有哈希冲突(无相同哈希值的key),直接插入;若有冲突,插入到桶对应的链表/红黑树中;

  4. 查找时,同样通过key计算哈希值、确定桶,再遍历桶中的链表/红黑树,找到对应key。

2. 哈希冲突与解决方式

哈希冲突:不同的key,通过哈希函数计算出相同的哈希值,导致要放入同一个桶中。

STL中unordered_map 解决哈希冲突的方式:链地址法(桶+链表/红黑树):

当桶中元素较少时,用链表存储冲突元素;

当桶中元素较多(超过阈值),链表会自动转为红黑树,将查找时间复杂度从O(n)优化为O(logn)。

3. 底层优势与劣势

  • 优势:平均查找、插入、删除效率O(1),比map(O(logn))更快;

  • 劣势:

    • 无序存储,无法按key排序;

    • 哈希函数有开销,且存在哈希冲突,最坏情况效率下降为O(n);

    • 内存占用比map高(需要存储哈希桶、链表/红黑树结构)。

六、unordered_map 与 map 核心对比(面试必背)

unordered_mapmap 都是键值对容器,key唯一,但底层实现、性能、有序性差异极大,是面试高频考点,用表格清晰对比:

对比项 unordered_map map
底层实现 哈希表(桶+链表/红黑树) 红黑树(有序平衡二叉树)
有序性 无序(存储顺序与插入顺序无关) 有序(按key升序排列,可自定义排序)
查找效率 平均O(1),最坏O(n) O(logn)(稳定)
插入/删除效率 平均O(1),最坏O(n) O(logn)
迭代器类型 双向迭代器 双向迭代器
key是否可修改 不可修改 不可修改
内存占用 较高(哈希桶+冲突解决结构) 较低(仅红黑树节点)
适用场景 无需排序,追求高效查找/插入 需要按key排序,查找效率要求稳定

七、常见误区与避坑重点

1. 误区1:unordered_map 支持随机访问

错误:unordered_map 不支持随机访问,无法通过um[i]访问第i个元素,只能通过um[key]访问对应value;

正确:随机访问仅支持vector、deque,unordered_map、map、list均不支持。

2. 误区2:[] 查找不会修改容器

错误:用um[key]查找时,若key不存在,会自动插入该键值对(value为默认值),修改容器内容;

正确:仅判断key是否存在,用count();安全查找value,用find()at()(at()不存在抛异常)。

3. 误区3:key可以修改

错误:无论是unordered_map还是map,key都不可修改(修改key会破坏底层结构:哈希表的存储位置、红黑树的有序性);

正确:若需修改key,需先删除原键值对,再插入新的键值对。

4. 误区4:哈希冲突会导致程序崩溃

错误:STL已内置哈希冲突解决机制(链地址法),哈希冲突只会影响效率,不会导致程序崩溃;

优化:可通过reserve(n)提前设置哈希桶数量,减少哈希冲突(n为预计存储的元素个数)。

八、总结(核心考点浓缩,面试直接背)

  1. unordered_map 是STL**无序键值对容器**,key唯一,底层基于哈希表实现,平均查找/插入/删除效率O(1);

  2. 核心API:insert()/emplace()插入、find()查找、erase()删除、[]/at()访问value;

  3. 迭代器:双向迭代器,不支持随机访问,key不可修改,value可修改;

  4. 底层:哈希桶+链地址法解决哈希冲突,冲突严重时链表转红黑树;

  5. 与map区别:无序vs有序、O(1)vsO(logn)效率、高内存vs低内存;

  6. 常见坑:[]查找有副作用、不支持随机访问、key不可修改;

  7. 面试高频:底层实现(哈希桶)、与map的区别、哈希冲突解决方式、迭代器类型。

相关推荐
三品吉他手会点灯1 小时前
C语言学习笔记 - 17.C编程预备计算机专业知识 - 数据类型
c语言·笔记·学习
珹洺2 小时前
C++远程调用组件库JsonRpc(一)项目背景、核心概念与环境搭建
开发语言·c++·rpc
噜噜噜阿鲁~2 小时前
python学习笔记 | 7.4、高级特性-生成器
笔记·python·学习
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【部分背包问题】:部分背包问题
c++·算法·贪心·csp·信奥赛·部分背包问题
handler012 小时前
【Linux 笔记】GDB 调试速查手册
linux·运维·c语言·c++·笔记
九思十安2 小时前
HNU2026-算法设计与分析-笔记 3 摊还分析
笔记·算法
胡图图不糊涂^_^2 小时前
网络原理笔记
java·网络·笔记·学习·tcp/ip·http·https
无忧.芙桃2 小时前
现代C++讲解之enum class,static_assert,tuple的使用
开发语言·c++
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 142. 环形链表 II | C++ 哈希表直觉解法
c++·leetcode·链表