开篇介绍:
hello 大家,那么在本篇博客中,我们将学习C++中unordered_set的使用方法,其实它的使用是和set差不多的,但是在实际中,它可能会更常用一些,所以还是需要大家去进行详细的了解的,知己知彼,方能百战百胜!!!
这里再顺便说一下,关于unordered_multiset,大家可以自行了解一下,简单的很。
一、unordered_set 核心特性铺垫
在啃接口之前,先把 unordered_set 的核心属性搞明白,这样接口的设计逻辑就一目了然了,不用死记硬背:
-
底层是哈希表 (哈希桶结构),增删查的平均时间复杂度 O(1),最坏情况 O(N)(哈希冲突严重时);
-
元素无序存储,遍历结果不固定(取决于哈希函数和负载因子);
-
自带去重特性,相同元素无法重复插入(和 set 一致);
-
迭代器是单向迭代器(只能 ++,不支持 -- 和随机访问,比如 it+1 是非法的);
-
迭代器本质是"常量迭代器",无法通过迭代器修改元素(会破坏哈希表结构,和 set 一致);
-
支持桶操作(哈希表特有),可通过桶相关接口查看哈希表状态(如桶数量、负载因子等)。
二、构造函数(创建 unordered_set 容器)
核心作用:初始化 unordered_set,可指定元素、哈希函数、比较函数(默认无需手动指定),是使用它的第一步。和 set 类似,但多了哈希相关的自定义参数(一般默认即可)。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 无参构造 unordered_set<T> s; | 创建空的 unordered_set,默认哈希函数和比较函数 | 无参数,返回空容器 | 后续通过 insert 插入元素 | 默认用 hash<T> 哈希函数、equal_to<T> 比较函数(去重) | unordered_set<int> s; s.insert(3); s.insert(1);(结果无序,如 {3,1}) |
| 自定义哈希/比较 unordered_set<T, Hash, Pred> s; | 按指定哈希函数和比较函数创建容器 | Hash:哈希函数(默认 hash<T>);Pred:比较函数(默认 equal_to<T>) | 自定义数据类型(如结构体)需手动实现哈希函数 | 哈希函数需保证"相同元素哈希值相同",比较函数需判断元素是否相等 | // 自定义字符串哈希(示例,实际可用默认)unordered_set<string, hash<string>> s; |
| 迭代器区间 unordered_set<T> s(first, last); | 从其他容器区间 [first, last) 拷贝元素 | 参数:源容器迭代器区间;返回初始化后的容器 | 批量导入其他容器元素 | 自动去重,元素无序存储(和源容器顺序无关) | vector<int> v={5,3,5}; unordered_set<int> s(v.begin(), v.end());(结果:{3,5},无序) |
| 初始化列表 unordered_set<T> s({elem1,...}); | 用 {} 直接初始化容器 | 参数:元素列表;返回初始化后的容器 | 创建时已知所有元素,简洁高效 | 支持隐式转换,自动去重 | unordered_set<string> s={"banana", "apple"};(结果无序,如 {"apple", "banana"}) |
| 拷贝构造 unordered_set<T> s(other); | 复制另一个 unordered_set 的元素和哈希/比较规则 | 参数:已存在的 unordered_set 容器;返回拷贝后的容器 | 复用已有容器的数据 | 原容器不变,拷贝后的容器元素无序(和原容器一致) | unordered_set<int> s1={1,2}; unordered_set<int> s2(s1);(s2 结果:{1,2},无序) |
三、迭代器函数(遍历 unordered_set 元素)
核心作用:获取容器的"遍历起点/终点",支持正向遍历(不支持反向,因为无序),是遍历元素的核心工具。重点注意:迭代器是单向的,只能 ++。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| begin()/end() | begin() 指向首元素,end() 指向尾后(不指向任何元素) | 返回单向迭代器 | 正向遍历所有元素 | end() 不可解引用,循环条件为 it != s.end();迭代器仅支持 ++ | unordered_set<int> s={1,2,3};for (auto it = s.begin(); it != s.end(); ++it) { cout << *it; }(输出无序,如 2 1 3) |
| cbegin()/cend() | 只读迭代器,功能同 begin()/end() | 返回常量单向迭代器 | 仅读取元素,不修改容器 | 更安全,避免误修改元素(unordered_set 本身也无法通过迭代器修改) | for (auto it = s.cbegin(); it != s.cend(); ++it) { cout << *it; } |
| 范围 for 遍历(C++11+) | 语法糖,简化遍历逻辑 | 无参数,直接遍历容器内所有元素 | 无需关注迭代器,快速遍历 | 加 const& 避免拷贝,提升效率 | for (const auto& e : s) { cout << e; }(输出无序) |
四、插入函数(向 unordered_set 添加元素)
核心作用:向容器中添加元素,自动完成去重(哈希表判断相等),支持单个、批量等多种插入方式,是填充数据的核心接口。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 单个插入 pair<iterator, bool> insert(val); | 插入单个元素,返回插入结果 | 返回 pair:- first:元素迭代器(插入成功则指向新元素,失败则指向已有元素);- second:插入成功标志(true=成功,false=重复) | 插入单个元素,需要判断是否插入成功 | 重复元素插入失败,返回已有元素的迭代器 | unordered_set<int> s={1,2};auto ret = s.insert(3); cout << ret.second;(输出 true)ret = s.insert(2); cout << ret.second;(输出 false) |
| 位置提示 insert(pos, val); | 传入提示位置插入元素(哈希表会忽略提示,仍按哈希值存储) | 参数:提示迭代器 + 元素值;返回元素迭代器(无 bool 值) | 大致知道元素存储位置,理论上优化效率(实际提升有限) | 提示位置不影响最终存储位置,仅可能减少哈希查找次数 | auto it = s.insert(s.begin(), 4); cout << *it;(输出 4) |
| 批量插入(区间) insert(first, last); | 插入其他容器区间 [first, last) 的元素 | 参数:源容器迭代器区间;无返回值 | 从其他容器批量导入元素 | 自动去重,元素无序存储;区间为左闭右开(不包含 last) | vector<int> v={3,4,2,5}; s.insert(v.begin(), v.end());(结果去重,无序) |
| 批量插入(列表) insert({elem1,...}); | 用 {} 包裹多个元素,一次性插入 | 参数:初始化列表;无返回值 | 已知多个插入元素,简洁高效 | 自动去重,支持直接写元素列表 | s.insert({6,7,3});(3 重复,仅插入 6、7) |
五、删除函数(从 unordered_set 移除元素)
核心作用:删除容器中的元素,支持按位置、按值、按区间删除,删除后哈希表会自动调整桶结构,保证后续操作高效。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 按位置删除 iterator erase(pos); | 删除迭代器指向的元素,返回下一个元素迭代器 | 参数:元素迭代器;返回下一个元素的迭代器 | 已知元素的迭代器位置(如 find 找到后删除) | 不可传入 end() 迭代器(非法);需先通过 find 确认元素存在 | auto it = s.find(2);if (it != s.end()) s.erase(it);(删除 2) |
| 按值删除 size_type erase(val); | 删除值为 val 的元素,返回删除的个数 | 参数:元素值;返回 size_t 类型(0=未找到,1=删除成功) | 知道元素值,直接删除(最常用) | 元素不存在时返回 0,不报错;因去重特性,最多删除 1 个 | size_t del = s.erase(3); cout << del;(存在返回 1,否则 0) |
| 按区间删除 erase(first, last); | 删除区间 [first, last) 的所有元素,返回下一个元素迭代器 | 参数:迭代器区间;返回下一个元素的迭代器 | 批量删除一段连续元素(需通过迭代器定位范围) | 左闭右开区间;因无序,"连续"是迭代器遍历顺序的连续,非元素值连续 | auto first = s.begin(); auto last = s.find(5);if (last != s.end()) s.erase(first, last);(删除从 begin 到 5 之前的元素) |
| 清空 clear(); | 删除容器中所有元素,清空容器 | 无参数;无返回值 | 需清空整个容器,重新填充数据 | 清空后 size() 为 0,桶结构保留(不会删除桶) | s.clear(); cout << s.empty();(输出 true) |
六、查找函数(查询 unordered_set 中的元素)
核心作用:查询元素是否存在、获取元素位置,基于哈希表实现,平均效率 O(1),比 set 的 O(logN) 更快,是 unordered_set 的核心优势。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| 精准查找 iterator find(val); | 查找值为 val 的元素,返回其迭代器 | 参数:元素值;返回迭代器(找到=指向元素,未找到=end()) | 确认元素存在并定位其位置 | 平均效率 O(1),远优于算法库的 find(O(N)) | auto it = s.find(3);if (it != s.end()) cout << *it;(找到输出 3) |
| 统计个数 size_type count(val); | 统计值为 val 的元素个数 | 参数:元素值;返回 size_t 类型(0=不存在,1=存在) | 快速判断元素是否存在(比 find 更简洁) | 因去重特性,返回值只有 0 或 1;本质是通过哈希查找,效率和 find 一致 | if (s.count(2)) cout << "元素 2 存在";(存在则输出) |
七、容量操作(获取容器状态)
核心作用:获取容器的元素个数、判断是否为空、获取最大存储容量等,辅助我们管理容器。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| size() | 返回当前容器中的元素个数 | 无参数;返回 size_t 类型(非负整数) | 想知道容器中元素的数量 | 不可用于判断空容器(建议用 empty(),更高效) | unordered_set<int> s={1,2,3}; cout << s.size();(输出 3) |
| empty() | 判断容器是否为空(无元素) | 无参数;返回 bool 类型(true=空,false=非空) | 避免对空容器进行遍历、删除等操作(防止崩溃) | 比 size() == 0 更高效(直接判断标志位,无需计算元素个数) | if (!s.empty()) { /* 遍历容器 */ } |
| max_size() | 返回理论上可存储的最大元素数 | 无参数;返回 size_t 类型(极大值,取决于系统和内存) | 评估容器的最大存储能力(实际开发中极少用到) | 仅为理论值,实际存储量受内存限制,可能小于该值 | cout << s.max_size();(输出如 18446744073709551615) |
八、哈希表特有操作(桶操作)
这是 unordered_set 独有的操作(set 没有),用于查看和调整哈希表的桶结构,帮助我们理解哈希表的工作原理,优化性能。
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| bucket_count() | 返回当前哈希表的桶数量 | 无参数;返回 size_t 类型 | 查看哈希表的桶数量,评估哈希表的填充程度 | 桶数量是 2 的幂次方(多数编译器实现),默认初始桶数较小(如 8、11) | unordered_set<int> s={1,2,...,10}; cout << s.bucket_count();(输出如 11) |
| bucket(val) | 返回值为 val 的元素所在的桶编号(从 0 开始) | 参数:元素值;返回 size_t 类型(桶编号) | 定位元素所在的桶,分析哈希冲突 | 元素不存在时,返回的桶编号无意义(需先通过 find 确认存在) | int val=5; cout << s.bucket(val);(输出 5 所在的桶编号) |
| bucket_size(n) | 返回第 n 个桶中的元素个数 | 参数:桶编号 n;返回 size_t 类型(该桶的元素个数) | 查看某个桶的填充情况,判断是否存在严重哈希冲突 | 桶编号 n 需小于 bucket_count()(否则行为未定义) | size_t n=5; cout << s.bucket_size(n);(输出桶 5 中的元素个数) |
| load_factor() | 返回当前负载因子(元素个数 / 桶数量,浮点数) | 无参数;返回 float 类型 | 评估哈希表的填充程度,负载因子越高,哈希冲突概率越大 | 负载因子超过 max_load_factor() 时,哈希表会自动扩容(rehash) | cout << s.load_factor();(如 10 个元素、11 个桶,输出 ~0.909) |
| max_load_factor() | 返回/设置最大负载因子(默认 1.0) | 无参数时返回 float 类型;有参数时设置为指定值(如 0.7) | 调整哈希表的扩容阈值,平衡时间和空间效率 | 设置更小的 max_load_factor() 可减少哈希冲突,但会增加桶数量(占用更多空间) | cout << s.max_load_factor();(默认输出 1.0)s.max_load_factor(0.7f);(设置最大负载因子为 0.7) |
| rehash(n) | 强制将桶数量设置为 n(n≥当前桶数量) | 参数:目标桶数量 n;无返回值 | 手动扩容哈希表,避免多次自动扩容(提升效率) | n 需大于等于当前桶数量;若 n 小于当前桶数量,该函数无效 | s.rehash(20); cout << s.bucket_count();(输出 20) |
| reserve(n) | 预留空间,确保桶数量足够存储 n 个元素而不触发自动扩容 | 参数:预计存储的元素个数 n;无返回值 | 批量插入大量元素前预留空间,减少自动扩容次数 | 会根据 max_load_factor() 计算所需桶数量(n / max_load_factor()),向上取整 | s.reserve(15);(确保能存储 15 个元素而不扩容) |
九、其他常用操作
| 函数形式 | 功能说明 | 参数 / 返回值 | 适用场景 | 注意点 | 示例代码 |
|---|---|---|---|---|---|
| swap(unordered_set& x); | 交换两个 unordered_set 的元素和哈希/比较规则 | 参数:另一个同类型的 unordered_set 容器;无返回值 | 快速交换两个容器的数据(无需手动拷贝) | 时间复杂度 O(1)(仅交换底层指针,不拷贝元素) | unordered_set<int> s1={1,2}, s2={3,4};s1.swap(s2);(s1 变为 {3,4},s2 变为 {1,2}) |
十、示例代码(实战演练)
下面用完整的代码演示 unordered_set 的核心用法,直接复制到编译器就能运行,注释详细,跟着走一遍就懂了~
cpp
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#include <set>
#include <map>
#include <cstdio>
#include <vector>
using namespace std;
// 那么本文件我们就来使用一下哈希表
// 也就是unordered_set和unordered_map
// 其实不难发现,它们也是set和map,只不过说前面加了个unordered,意思是无序的
// 那么就是因为set和map能够通过中序遍历呈现出有序的结果,而哈希表不行
// 所以就是unorder,那么其余的其实是和基于红黑树的set和map差不多
// 甚至可以说是一模一样(夸张)
// 同样的,unordered_set存储key值
// unordered_map存储key,value,也就是pair<key,value>
// unordered_set默认要求Key支持转换为整型,注意stl库中也将string可以转换为整型
// 所以要是我们给unordered_set传入string变量的话,也没事,不用我们自己实现仿函数
// 如果不支持或者想按自己的需求走可以自行实现支持将Key转成整型的仿函数传给第二个模板参数
// unordered_set默认要求Key支持比较相等,
// 如果不支持或者想按自己的需求走可以自行实现支持将Key比较相等的仿函数传给第三个模板参数
// template < class Key, //
// unordered_set::key_type/value_type
// class Hash = hash<Key>, // unordered_set::hasher
// class Pred = equal_to<Key>, // unordered_set::key_equal
// class Alloc = allocator<Key> // unordered_set::allocator_type
// > class unordered_set;
/*
* unordered_set与set的核心差异详解
* 1. 使用层面的一致性
* 查看文档可知,unordered_set支持增加、删除、查找三类核心操作,且其调用语法、接口命名、参数传递方式
* 与set完全一致(例如插入用insert()、删除用erase()、查找用find()等)。基于这一高度一致性,
* 关于unordered_set的具体使用方法(如函数调用、返回值处理等),此处不再赘述和演示。
*
* 2. 第一个核心差异:对Key类型的要求不同
* - set对Key的要求:set底层依赖红黑树(二叉搜索树)实现,红黑树的插入、查找、删除等操作需要通过
* "小于比较"来确定元素的位置,因此要求Key类型必须支持小于比较操作(内置类型int/string默认支持,
* 自定义类型需重载operator<运算符)。
* - unordered_set对Key的要求:unordered_set底层基于哈希表实现,哈希表的核心是通过哈希函数将Key转换为
* 整形哈希值(确定存储位置),同时处理哈希冲突时需要判断两个Key是否相等,因此要求Key满足两点:
* ① 支持通过哈希函数转换为整形(内置类型默认支持,自定义类型需自定义哈希函数);
* ② 支持等于比较操作(自定义类型需重载operator==运算符)。
* - 补充说明:unordered_set对Key的这两点要求并非自身额外限制,而是哈希表底层实现的本质要求,
* 需后续结合哈希表底层逻辑才能真正理解。
*
* 3. 第二个核心差异:迭代器特性与遍历结果不同
* 该差异包含"迭代器类型"和"遍历特性"两个维度:
* - 迭代器类型差异:set的iterator属于双向迭代器,支持"++"(向后遍历)和"--"(向前遍历)操作;
* unordered_set的迭代器仅为单向迭代器,仅支持"++"向后遍历,不支持"--"向前遍历。
* - 遍历结果特性差异:
* ① set:底层是红黑树(有序二叉搜索树),元素按Key大小排序存储,迭代器遍历本质是红黑树的中序遍历,
* 因此遍历结果呈现"有序+去重"的特点(无重复元素,且按Key大小有序);
* ② unordered_set:底层是哈希表,元素存储位置由哈希值决定(与Key大小、插入顺序均无关),
* 因此迭代器遍历结果呈现"无序+去重"的特点(无重复元素,但遍历顺序无规律)。
* 二者都可以去重哦!!!
*
* 4. 第三个核心差异:增删查改的性能表现不同
* - 整体性能趋势:在绝大多数业务场景下(无极端哈希冲突、数据量较大),unordered_set的增删查改效率优于set。
* - 底层效率支撑:
* ① set(红黑树):增删查改操作需要维护树的平衡,时间复杂度为O(logN)(N为元素个数);
* ② unordered_set(哈希表):无哈希冲突的理想场景下,增删查操作平均时间复杂度为O(1)(常数级),
* 即使存在少量哈希冲突,效率也远高于红黑树。
* - 验证方式:二者具体的性能差异(如操作耗时、执行次数对比),可参考下方代码的实际运行演示结果。
*/
using namespace std;
int test_set2()
{
const size_t N = 1000000;
unordered_set<int> us;
set<int> s;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; ++i)
{
// v.push_back(rand()); // N比较大时,重复值比较多
v.push_back(rand() + i); // 重复值相对少
// v.push_back(i); // 没有重复,有序
}
// 21:15
size_t begin1 = clock();
for (auto e : v)
{
s.insert(e);
}
size_t end1 = clock();
cout << "set insert:" << end1 - begin1 << endl;
size_t begin2 = clock();
us.reserve(N);
for (auto e : v)
{
us.insert(e);
}
size_t end2 = clock();
cout << "unordered_set insert:" << end2 - begin2 << endl;
int m1 = 0;
size_t begin3 = clock();
for (auto e : v)
{
auto ret = s.find(e);
if (ret != s.end())
{
++m1;
}
}
size_t end3 = clock();
cout << "set find:" << end3 - begin3 << "->" << m1 << endl;
int m2 = 0;
size_t begin4 = clock();
for (auto e : v)
{
auto ret = us.find(e);
if (ret != us.end())
{
++m2;
}
}
size_t end4 = clock();
cout << "unorered_set find:" << end4 - begin4 << "->" << m2 << endl;
cout << "插入数据个数:" << s.size() << endl;
cout << "插入数据个数:" << us.size() << endl
<< endl;
size_t begin5 = clock();
for (auto e : v)
{
s.erase(e);
}
size_t end5 = clock();
cout << "set erase:" << end5 - begin5 << endl;
size_t begin6 = clock();
for (auto e : v)
{
us.erase(e);
}
size_t end6 = clock();
cout << "unordered_set erase:" << end6 - begin6 << endl
<< endl;
return 0;
}
int main()
{
test_set2();
return 0;
}
// 它们的使用可以说是和set和map一样,所以下面就简单示例一下
// 简单示例unorder_map,至于另一个,我们放在另一个文件
int main() {
/************************ 一、构造函数:创建unordered_set对象 ************************/
cout << "===== 1. 构造函数示例 =====" << endl;
// 1. 无参构造:创建空的unordered_set,底层哈希表默认桶数(不同编译器可能不同,如VS默认8)
// 特性:存储int类型,默认哈希函数hash<int>(),默认等于比较equal_to<int>(),去重+无序
unordered_set<int> us1;
cout << "us1(无参构造)是否为空:" << boolalpha << us1.empty() << endl; // boolalpha让bool输出true/false
// 2. 迭代器区间构造:从其他容器的迭代器区间初始化
// 步骤:先创建vector,再用vector的[begin, end)区间构造unordered_set
vector<int> vec = {10, 20, 30, 20, 40}; // 包含重复元素20
unordered_set<int> us2(vec.begin(), vec.end());
// 特性:自动去重(20只保留一个),遍历无序(输出顺序与vec无关)
cout << "us2(迭代器构造)元素:";
for (auto e : us2)
cout << e << " "; // 示例输出:10 20 30 40(顺序可能不同)
cout << endl;
// 3. 初始化列表构造(C++11+):最常用的构造方式,直接传{}包裹的元素
// 特性:支持任意可哈希类型(如int/string),自动去重
unordered_set<int> us3 = {1, 2, 3, 2, 4}; // 重复元素2被去重
cout << "us3(初始化列表构造)元素:";
for (auto e : us3)
cout << e << " "; // 示例输出:1 2 3 4(顺序随机)
cout << endl;
// 4. 拷贝构造:复制已有unordered_set的所有元素
unordered_set<int> us4(us3); // 完全复制us3的元素、哈希表结构
cout << "us4(拷贝构造)元素个数:" << us4.size() << endl; // 输出4
// 5. 移动构造(C++11+):接管已有unordered_set的资源(us5变为空)
// 语法:std::move将左值转为右值,避免拷贝,效率更高
unordered_set<int> us5(move(us3));
cout << "移动构造后,原us3是否为空:" << us3.empty() << endl; // 输出true(us3资源被移走)
cout << "us5(移动构造)元素:";
for (auto e : us5)
cout << e << " "; // 输出原us3的元素
cout << endl;
// 6. 自定义桶数构造:指定初始桶的数量(减少后续rehash次数)
// 语法:unordered_set<T> us(n),n为初始桶数
unordered_set<string> us6(10); // 初始桶数10,存储string类型
us6.insert("apple");
cout << "us6(指定桶数构造)的桶数量:" << us6.bucket_count() << endl; // 输出10(未触发rehash)
/************************ 二、插入操作:insert接口 ************************/
cout << "\n===== 2. 插入操作(insert)示例 =====" << endl;
unordered_set<int> insert_us;
// 1. 单元素插入:insert(val),返回值为pair<iterator, bool>
// 返回值解析:
// - pair.first:指向插入/已存在元素的迭代器(去重特性:已存在则返回已有元素迭代器)
// - pair.second:bool值,true=插入成功(元素不存在),false=插入失败(元素已存在)
auto ret1 = insert_us.insert(10); // 插入新元素10
cout << "插入10是否成功:" << ret1.second << ",插入位置的元素:" << *ret1.first << endl; // true 10
auto ret2 = insert_us.insert(10); // 插入重复元素10
cout << "插入重复10是否成功:" << ret2.second << ",返回的迭代器指向:" << *ret2.first << endl; // false 10
// 2. 指定迭代器插入:insert(hint, val)
// 参数解析:
// - hint:迭代器,仅作为"提示位置"(哈希表会忽略,仍按哈希值存储)
// 返回值:指向插入/已存在元素的迭代器(无bool值)
auto it_hint = insert_us.insert(insert_us.begin(), 20); // 提示在begin位置插入20
cout << "指定迭代器插入20,返回迭代器指向:" << *it_hint << endl; // 20
// 3. 区间插入:insert(first, last)
// 作用:将[first, last)区间内的所有元素插入,自动去重,无返回值
vector<int> insert_vec = {30, 40, 20, 50}; // 20是重复元素
insert_us.insert(insert_vec.begin(), insert_vec.end());
cout << "区间插入后,insert_us元素:";
for (auto e : insert_us)
cout << e << " "; // 示例:10 20 30 40 50(无序)
cout << endl;
// 4. 初始化列表插入(C++11+):insert({val1, val2, ...})
// 作用:批量插入多个元素,自动去重,无返回值
insert_us.insert({60, 70, 30}); // 30重复
cout << "初始化列表插入后,insert_us元素:";
for (auto e : insert_us)
cout << e << " "; // 示例:10 20 30 40 50 60 70(无序)
cout << endl;
/************************ 三、删除操作:erase接口 ************************/
cout << "\n===== 3. 删除操作(erase)示例 =====" << endl;
unordered_set<int> erase_us = {10, 20, 30, 40, 50};
cout << "删除前,erase_us元素:";
for (auto e : erase_us) cout << e << " ";
cout << endl;
// 1. 按迭代器删除:erase(pos)
// 参数:指向要删除元素的迭代器,无返回值
// 注意:unordered_set是单向迭代器,仅支持++,不支持--/随机访问
auto it_erase = erase_us.find(20); // 先找到20的迭代器
if (it_erase != erase_us.end())
{ // 必须判断迭代器是否有效(避免删除end())
erase_us.erase(it_erase);
cout << "按迭代器删除20后,erase_us元素:";
for (auto e : erase_us) cout << e << " "; // 无20
cout << endl;
}
// 2. 按key删除:erase(val)
// 返回值:size_t类型,删除的元素个数(unordered_set去重,故返回0或1)
size_t del_count1 = erase_us.erase(30); // 删除存在的30
cout << "按key删除30,删除个数:" << del_count1 << endl; // 1
size_t del_count2 = erase_us.erase(100); // 删除不存在的100
cout << "按key删除100,删除个数:" << del_count2 << endl; // 0
// 3. 区间删除:erase(first, last)
// 作用:删除[first, last)区间内的所有元素,无返回值
// 步骤:先找到起始和结束迭代器(示例:删除从begin到find(50)的区间)
auto it_begin = erase_us.begin();
auto it_end = erase_us.find(50);
if (it_end != erase_us.end())
{
erase_us.erase(it_begin, it_end);
cout << "区间删除后,erase_us剩余元素:";
for (auto e : erase_us) cout << e << " "; // 仅剩余50
cout << endl;
}
/************************ 四、查找操作:find/count ************************/
cout << "\n===== 4. 查找操作(find/count)示例 =====" << endl;
unordered_set<int> find_us = {10, 20, 30, 40, 50};
// 1. find接口:find(val)
// 作用:查找key为val的元素,返回迭代器
// 返回值:找到=指向该元素的迭代器;未找到=end()迭代器
auto it_find = find_us.find(30);
if (it_find != find_us.end())
{
cout << "find(30)找到元素:" << *it_find << endl; // 30
}
else
{
cout << "find(30)未找到元素" << endl;
}
auto it_find_none = find_us.find(100);
if (it_find_none == find_us.end())
{
cout << "find(100)未找到元素" << endl;
}
// 2. count接口:count(val)
// 作用:统计key为val的元素个数(unordered_set去重,故返回0或1)
// 常用场景:快速判断元素是否存在(比find更简洁)
size_t count_20 = find_us.count(20);
cout << "count(20)结果:" << count_20 << " → 说明元素存在" << endl; // 1
size_t count_100 = find_us.count(100);
cout << "count(100)结果:" << count_100 << " → 说明元素不存在" << endl; // 0
/************************ 五、迭代器遍历 ************************/
cout << "\n===== 5. 迭代器遍历示例 =====" << endl;
unordered_set<string> iter_us = {"apple", "banana", "orange", "grape"};
// 1. 普通迭代器遍历(单向迭代器,仅支持++)
cout << "普通迭代器遍历:";
unordered_set<string>::iterator it_iter;
for (it_iter = iter_us.begin(); it_iter != iter_us.end(); ++it_iter)
{
cout << *it_iter << " "; // 输出无序,如apple grape banana orange
}
cout << endl;
// 2. const迭代器遍历:只读,不可修改元素(unordered_set的key本身不可修改)
cout << "const迭代器遍历:";
unordered_set<string>::const_iterator it_const;
for (it_const = iter_us.cbegin(); it_const != iter_us.cend(); ++it_const)
{
cout << *it_const << " "; // 与普通迭代器结果一致
}
cout << endl;
// 4. 范围for遍历(C++11+):最简洁的遍历方式
cout << "范围for遍历:";
for (const auto& e : iter_us)
{ // 加const&避免拷贝,提升效率
cout << e << " ";
}
cout << endl;
/************************ 六、容量操作:size/empty/max_size ************************/
cout << "\n===== 6. 容量操作示例 =====" << endl;
unordered_set<int> capacity_us = {10, 20, 30};
// 1. size():返回当前元素个数
cout << "capacity_us元素个数(size):" << capacity_us.size() << endl; // 3
// 2. empty():判断是否为空(size==0),返回bool
cout << "capacity_us是否为空(empty):" << boolalpha << capacity_us.empty() << endl; // false
// 3. max_size():返回理论上可存储的最大元素数(系统/内存限制)
cout << "capacity_us最大可存储元素数(max_size):" << capacity_us.max_size() << endl; // 大数,如18446744073709551615
// 4. clear():清空所有元素,size变为0,桶结构保留
capacity_us.clear();
cout << "clear后,capacity_us是否为空:" << capacity_us.empty() << endl; // true
cout << "clear后,capacity_us元素个数:" << capacity_us.size() << endl; // 0
/************************ 七、哈希表特有操作(桶操作) ************************/
cout << "\n===== 7. 哈希表桶操作示例(unordered_set特有) =====" << endl;
unordered_set<int> bucket_us = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 1. bucket_count():返回当前哈希表的桶数量
cout << "当前桶数量(bucket_count):" << bucket_us.bucket_count() << endl; // 示例:11(编译器默认)
// 2. bucket(val):返回key=val的元素所在的桶编号(从0开始)
int key = 5;
size_t bucket_num = bucket_us.bucket(key);
cout << "元素" << key << "所在桶编号:" << bucket_num << endl; // 示例:5
// 3. bucket_size(n):返回第n个桶中的元素个数
size_t bucket_size_num = bucket_us.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_us.load_factor();
cout << "当前负载因子(load_factor):" << load_fac << endl; // 示例:10/11≈0.909
// 5. max_load_factor():返回/设置最大负载因子(默认1.0)
float max_load_fac = bucket_us.max_load_factor();
cout << "默认最大负载因子(max_load_factor):" << max_load_fac << endl; // 1.0
// 设置最大负载因子为0.7(超过则自动rehash,增加桶数)
bucket_us.max_load_factor(0.7f);
cout << "设置后最大负载因子:" << bucket_us.max_load_factor() << endl; // 0.7
// 6. rehash(n):强制设置桶数量为n(n≥当前bucket_count)
bucket_us.rehash(20); // 桶数改为20
cout << "rehash(20)后,桶数量:" << bucket_us.bucket_count() << endl; // 20
// 7. reserve(n):预留空间,确保桶数足够存储n个元素而不触发rehash
// 区别于rehash:reserve根据负载因子计算所需桶数,rehash直接指定桶数
bucket_us.reserve(15);
cout << "reserve(15)后,桶数量:" << bucket_us.bucket_count() << endl; // 示例:22(15/0.7≈21.4,向上取整)
/************************ 八、其他操作:swap ************************/
cout << "\n===== 8. 交换操作(swap)示例 =====" << endl;
unordered_set<int> swap_us1 = {1, 2, 3};
unordered_set<int> swap_us2 = {10, 20, 30, 40};
cout << "交换前:" << endl;
cout << "swap_us1元素:";
for (auto e : swap_us1)
cout << e << " "; // 1 2 3
cout << endl;
cout << "swap_us2元素:";
for (auto e : swap_us2)
cout << e << " "; // 10 20 30 40
cout << endl;
// swap(us1, us2):交换两个unordered_set的内容,时间复杂度O(1)(仅交换底层指针)
swap_us1.swap(swap_us2);
cout << "交换后:" << endl;
cout << "swap_us1元素:";
for (auto e : swap_us1)
cout << e << " "; // 10 20 30 40
cout << endl;
cout << "swap_us2元素:";
for (auto e : swap_us2)
cout << e << " "; // 1 2 3
cout << endl;
return 0;
}
//那么还有unordered_multiset,使用方法和unordered_set一样,这里就不再多余赘述!!!
十一、核心总结
unordered_set 的接口设计围绕"无序、高效、去重"展开,核心亮点是哈希表带来的 O(1) 平均增删查效率,和 set 的"有序、O(logN) 效率"形成互补,根据场景选择即可:
1. 核心高频接口
-
构造:初始化列表构造(
unordered_set<int> s={1,2,3};),最简洁; -
遍历:范围 for 遍历(
for (const auto& e : s)),无需关注迭代器; -
插入:
insert(val)(单个插入,返回 pair 判断成功)、insert({elem1,...})(批量插入); -
删除:
erase(val)(按值删除,返回删除个数)、clear()(清空); -
查找:
find(val)(精准定位)、count(val)(快速判断存在); -
桶操作:
reserve(n)(批量插入前预留空间,优化效率)。
2. set vs unordered_set 选型建议
| 场景需求 | 选 set | 选 unordered_set |
|---|---|---|
| 元素需要有序 | ✅ | ❌ |
| 增删查效率优先(大数据量) | ❌(O(logN)) | ✅(O(1) 平均) |
| 需要范围查找(如 lower_bound) | ✅ | ❌(无序,无该接口) |
| 内存占用敏感 | ✅(红黑树内存开销小) | ❌(哈希表需预留桶空间) |
3. 注意事项
-
迭代器是单向的,仅支持 ++,不支持 -- 和随机访问(如 it+1 非法);
-
元素无序,遍历结果不固定(取决于哈希函数);
-
自定义数据类型(如结构体)需手动实现哈希函数和比较函数;
-
批量插入大量元素前,用
reserve(n)预留空间,减少自动扩容次数,提升效率。
结语:以高效为刃,破无序场景之局
哈喽各位小伙伴,咱们关于 unordered_set 的学习到这里就圆满结束啦!回顾一下,咱们从底层哈希表的逻辑出发,吃透了它"无序但高效"的核心特性,拆解了构造、迭代器、增删查、桶操作等核心接口,还通过示例代码实战了用法,最后明确了和 set 的选型边界------这就是技术学习的"从底层到上层"的思路,搞懂了底层逻辑,接口用法自然不用死记硬背。
unordered_set 最大的价值,就是在"无需有序"的场景下,用 O(1) 的平均效率帮我们快速解决问题。比如统计不重复数据、判断元素是否存在、快速增删元素这些场景,它比 set 更高效,是我们开发中的"效率利器"。而桶操作相关的接口,虽然平时用得不多,但理解它们能帮我们更深入地掌握哈希表的工作原理,比如负载因子、扩容机制,这些知识在后续学习哈希表、分布式缓存等内容时都会用到,是技术积累的"复利"。
可能有小伙伴会问,"那 unordered_set 有对应的'允许重复'版本吗?" 答案是有的!就是 unordered_multiset,它和 unordered_set 的关系,就像 multiset 和 set 的关系------核心差异是允许重复元素,其他接口用法几乎一致(比如 insert 不返回 bool,erase 按值删除会删除所有重复元素)。如果后续有需求,咱们再专门聊它,现在先把 unordered_set 吃透就好~
技术学习从来不是"学完就忘",而是要"融会贯通"。咱们今天学的 unordered_set,和之前学的 set、vector、list 等容器,共同构成了 C++ STL 的容器体系。不同的容器有不同的底层实现和适用场景,学会根据需求选择合适的容器,是从"会写代码"到"会写好代码"的关键一步。比如处理"去重+有序"用 set,"去重+高效"用 unordered_set,"随机访问+连续内存"用 vector,"频繁插入删除"用 list------这些选型逻辑,需要我们在实践中不断强化。
最后,还是要鼓励大家多动手实践。示例代码里的每个函数,都可以复制到编译器里运行一遍,修改一下参数(比如插入重复元素、删除不存在的元素),看看输出结果的变化,这样才能真正理解接口的用法。遇到疑惑也不用怕,比如"为什么迭代器只能 ++?""负载因子怎么影响效率?",多查文档、多调试,慢慢就能豁然开朗。