请注意,本文尚未完成!您可以通过编辑此文档页面来提供帮助。
概述
- 简化版 (C++98 起)
- 详细
template< class Key, /* ... */ >
class unordered_set;
- 常规版 (C++98 起)
- 多态版 (C++17 起)
template<
class Key,
class Hash = std::hash<Key>,
class Compare = std::equal_to<Key>,
class Allocator = std::allocator<Key>
>
class unordered_set;
namespace pmr {
template <class Key,
class Hash = std::hash<Key>,
class Compare = std::equal_to<Key>>
using unordered_set = std::unordered_set<Key, Hash, Compare,
std::pmr::polymorphic_allocator<Key>>;
}
std::unordered_set
是一个用于存储唯一对象的关联容器。
使用示例
本节中的示例非常简单。有关更多示例,请导航到底部的示例部分。
- 创建
- 插入
- 搜索
- C++17
- C++17 之前
- 从范围
使用CTAD
#include <unordered_set>
int main() {
std::unordered_set ints{1, 2, 3, 2, 1, 4, 5, 5};
// Content: 1, 2, 3, 4, 5
}
即使我们在构造时重复元素,元素仍将保持唯一。
#include <unordered_set>
int main() {
std::unordered_set<int> ints{1, 2, 3, 2, 1, 4, 5, 5};
// Content: 1, 2, 3, 4, 5
}
我们可以使用迭代器从其他容器构造几乎所有容器。
#include <unordered_set>
#include <array>
int main() {
std::array<int, 5> array{1, 4, 3, 3, 2};
std::unordered_set<int> ints(array.cbegin(), array.cend());
// Content: 1, 2, 3, 4
}
重要的是要注意,尽管示例注释中选择了任意顺序,但内部元素保持无序(正如容器名称所示),这意味着我们在遍历无序集合时不能保证有任何特定顺序。
- 普通
- 一次多个
- 从范围
无论如何,集合将始终保持唯一。
#include <unordered_set>
int main() {
std::unordered_set<int> ints;
ints.insert(1);
ints.insert(2);
ints.insert(1);
ints.insert(2);
// Content: 1 2
}
我们可以一次插入多个值。
#include <unordered_set>
int main() {
std::unordered_set<int> ints;
ints.insert({4, 3, 2, 1, 1});
// Content: 1, 2, 3, 4
}
我们也可以像在构造中一样,从其他范围插入。
#include <unordered_set>
#include <array>
int main() {
std::array<int, 5> array = {1, 2, 3, 3, 4};
std::unordered_set<int> ints;
ints.insert(array.cbegin(), array.cend());
// Content: 1, 2, 3, 4
}
- 包含(自 C++20 起)
- 计数
- 查找
自 C++20 起,我们可以简单地在集合上使用 .contains()
来确定是否存在某个任意值。
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> ints{1, 2, 3, 4, 5};
if(ints.contains(3)) {
std::cout << "3 exists in the set.";
} else {
std::cout << "3 doesn't exist in the set.";
}
}
.count()
是确定集合中元素存在的另一种方法。
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> ints{1, 2, 3, 4, 5};
if(ints.count(3) == 1) { // or simply if(ints.count(3)) , same effect
std::cout << "3 exists in the set.";
} else {
std::cout << "3 doesn't exist in the set.";
}
}
我们可以使用 .find()
方法在集合中查找特定值。如果该值不在集合中,则返回的迭代器将等于结束迭代器(通过 .end()
或 .cend()
获取)。
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> ints{1, 2, 3, 4, 5};
const auto it = ints.find(3); // we can use auto to deduce the type of the iterator returned
if(it != ints.cend()) {
// if the iterator is not equal to ints.cend(), the value is in the set
std::cout << "3 exists in the set.";
} else {
std::cout << "3 doesn't exist in the set.";
}
}
技术细节
unordered_set 的技术定义
std::unordered_set
是一个关联容器,包含一组类型为 Key
的唯一对象。
搜索、插入和移除具有平均常数时间复杂度。
在内部,元素不按任何特定顺序排序,而是组织成桶。元素放置在哪个桶中完全取决于其值的哈希。这允许快速访问单个元素,因为一旦计算出哈希,它就指向元素所放置的确切桶。
容器元素不能被修改(即使是非 const 迭代器),因为修改可能会改变元素的哈希并破坏容器。
std::unordered_set
符合 Container
、AllocatorAwareContainer
、UnorderedAssociativeContainer
的要求。
std::unordered_set
定义于 | unordered_set |
模板参数
公有 | Key | 存储的键的类型。 |
公有 | 哈希 | 一个对 |
公有 | Compare | 满足 Compare 的比较器类型。 |
公有 | Allocator | 负责分配和释放内存的分配器类型。必须满足 Allocator。 |
类型名称
公有 | key_type | Key |
公有 | value_type | Key |
公有 | size_type | 无符号整数类型(通常为 |
公有 | difference_type | 有符号整数类型(通常为 |
公有 | hasher | 哈希 |
公有 | key_equal | KeyEqual |
公有 | allocator_type | Allocator |
公有 | reference | value_type& |
公有 | const_reference | const value_type& |
公有 | pointer | std::allocator_traits<Allocator>::pointer |
公有 | const_pointer | std::allocator_traits<Allocator>::const_pointer |
公有 | iterator ❗ | 指向 |
公有 | const_iterator ❗ | 指向 |
公有 | local_iterator | 像 |
公有 | const_local_iterator | 像 |
公有 | node_type (C++17 起) | 表示容器节点的 节点句柄 的特化。 |
公有 | insert_return_type (C++17起) | 描述插入
使用模板参数 |
无序集合的迭代器
iterator
被称为 constant,因为它不可能修改其指向的值。这是因为修改迭代器指向的值可能会使无序集合的内部结构失效,从而导致各种不正确和意外的行为。
成员类型别名 iterator
和 const_iterator
可能相同。这完全取决于实现。这意味着任何仅在迭代器类型上不同的重载函数/特化可能是不正确的,并可能导致 ODR 违规。
迭代器失效
公有 | ||
公有 | 所有只读操作,swap, std::swap | 从不 |
公有 | Clear, rehash, reverse, operator= | 总是 |
公有 | Insert, emplace, emplace_hint | 仅当导致重新哈希时 |
公有 | Erase | 仅对被删除的元素 |
成员函数
公有 | (构造函数) | 构造一个新的无序集合。 |
公有 | (析构函数) | 销毁一个无序集合。 |
公有 | operator= | 将一个无序集合赋值给另一个。 |
公有 | get_allocator | 返回关联的分配器。 |
迭代器
公有 | begin cbegin | 返回指向开头的迭代器。 |
公有 | end cend | 返回指向末尾的迭代器。 |
容量
公有 | empty | 如果集合为空,则返回 |
公有 | size | 返回无序集合中的元素数量。 |
公有 | max_size | 返回最大可能的元素数量。 |
修饰符
公有 | clear | 清除无序集合的内容。 |
公有 | insert | 插入元素(或节点(用 |
公有 | emplace | 就地构造新元素。 |
公有 | emplace_hint | 使用提示(迭代器)就地构造元素。 |
公有 | erase | 擦除元素。 |
公有 | swap | 交换两个无序集合。 |
公有 | extract (C++17 起) | 从无序集合中提取节点(可以稍后插入到其他地方)。 |
公有 | merge (C++17 起) | 将两个无序集合合并在一起。 |
查找
公有 | count | 返回与特定键匹配的元素数量。 |
公有 | find | 搜索元素并返回指向它的迭代器,如果未找到则返回结束迭代器。 |
公有 | contains (C++20 起) | 如果元素在集合中,则返回 |
公有 | equal_range | 返回与特定键匹配的元素范围。 |
桶接口
公有 | begin (size_type) cbegin (size_type) | 返回指向指定桶开头的迭代器。 |
公有 | end (size_type) cend (size_type) | 返回指向指定桶末尾的迭代器。 |
公有 | bucket_count | 返回桶的数量。 |
公有 | max_bucket_count | 返回最大桶数量。 |
公有 | bucket_size | 返回特定桶中元素的数量。 |
公有 | bucket | 返回特定键的桶。 |
哈希策略
公有 | load_factor | 返回每个桶的平均元素数量。 |
公有 | max_load_factor | 管理每个桶的最大平均元素数量。 |
公有 | rehash | 至少保留指定数量的桶并重新生成哈希表。 |
公有 | reserve | 为至少指定数量的元素保留空间并重新生成哈希表。 |
观察器
公有 | hash_function | 返回哈希键的内部函数对象。 |
公有 | key_eq | 返回一个比较键的内部函数对象。 |
非成员函数
公有 | operator== operator!= (C++20 中移除) | 比较 unordered_map 中的值。 |
公有 | std::swap (std::unordered_set) | std::swap 算法的重载。 |
公有 | std::erase_if (std::unordered_set) (自 C++20 起) | std::erase_if 算法的重载。 |
推导指南 (C++17 起)
点击展开
推导指南
// (1)
template< class InputIt,
class Hash = std::hash<iter_val_t<InputIt>>,
class Pred = std::equal_to<iter_val_t<InputIt>>,
class Alloc = std::allocator<iter_val_t<InputIt>> >
unordered_set( InputIt, InputIt,
typename /*see below*/::size_type = /*see below*/,
Hash = Hash(), Pred = Pred(), Alloc = Alloc() )
-> unordered_set<iter_val_t<InputIt>, Hash, Pred, Alloc>;
// (2)
template< class InputIt, class Alloc >
unordered_set( InputIt, InputIt, typename /*see below*/::size_type, Alloc )
-> unordered_set<iter_val_t<InputIt>,
std::hash<iter_val_t<InputIt>>,
std::equal_to<iter_val_t<InputIt>>,
Alloc>;
// (3)
template< class InputIt, class Hash, class Alloc >
unordered_set( InputIt, InputIt, typename /*see below*/::size_type, Hash, Alloc )
-> unordered_set<iter_val_t<InputIt>, Hash,
std::equal_to<iter_val_t<InputIt>>, Allocator>;
(1 - 3)
允许从迭代器范围推导
// (4)
template< class T, class Allocator >
unordered_set( std::initializer_list<T>, typename /*see below*/::size_type, Allocator )
-> unordered_set<T, std::hash<T>, std::equal_to<T>, Alloc>;
// (5)
template< class T, class Hash, class Alloc >
unordered_set( std::initializer_list<T>, typename /*see below*/::size_type, Hash, Alloc )
-> unordered_set<T, Hash, std::equal_to<T>, Alloc>;
// (6)
template< class T,
class Hash = std::hash<T>,
class Pred = std::equal_to<T>,
class Alloc = std::allocator<T> >
unordered_set( std::initializer_list<T>,
typename /*see below*/::size_type = /*see below*/,
Hash = Hash(), Pred = Pred(), Alloc = Alloc() )
-> unordered_set<T, Hash, Pred, Alloc>;
(4- 6)
允许从std::initializer_list
推导
别名 iter_val_t
的定义如下
template< class InputIt >
using iter_val_t = typename std::iterator_traits<InputIt>::value_type;
请注意,此别名不保证在标准库的任何地方找到。它仅用于这些推导指南的演示目的,并且在编写本文档时不存在于标准库中。
size_type
参数类型指的是推导指南推导出的类型的 size_type
成员类型。
重载决议
为了使任何推导指南参与重载决议,必须满足以下要求
InputIt
满足LegacyInputIterator
Alloc
满足Allocator
Hash
不推导为整数类型或不满足Allocator
Pred
不满足Allocator
库确定类型不满足 LegacyInputIterator
的程度是未指定的,但至少
- 整型不符合输入迭代器的条件。
同样,它确定类型不满足 Allocator
的程度是未指定的,但至少
- 成员类型
Alloc::value_type
必须存在。 - 当作为未求值操作数处理时,表达式
std::declval<Alloc&>().allocate(std::size_t{})
必须是格式良好的。
更多示例
创建自定义哈希和比较器。
我们可以通过定义重载 operator()
的函数对象来创建自己的比较器。要为对象创建自定义哈希,我们可以在 std
命名空间中为其创建特化。
#include <iostream>
#include <string>
#include <unordered_set>
struct student {
std::string name;
std::string surname;
int age;
bool operator==(const student&) const noexcept = default;
};
/*
* We need to define a specialization for std::hash for the unordered_set to work
* properly.
*/
namespace std {
template <>
struct hash<student> {
std::size_t operator()(const student& obj) const noexcept {
constexpr auto str_hash = std::hash<std::string>();
return (str_hash(obj.name) * 31) ^ (str_hash(obj.surname) * 31) ^ obj.age;
}
};
}
struct student_comparator {
bool operator()(const student& first, const student& second) const noexcept {
return first == second;
}
};
auto main() -> int {
using student_set = std::unordered_set<student, std::hash<student>, student_comparator>;
const auto student1 = student("Philip", "Davies", 17);
const auto student2 = student("Philip", "Davies", 17);
const auto student3 = student("Philip", "Smith", 19);
const auto student4 = student("Jacob", "Jones", 19);
auto students = student_set{student1, student2, student3, student4};
for(const auto& student : students) {
std::cout << "Student: " << student.name << ' '
<< student.surname << ", "
<< student.age << " years old.\n";
}
}
Student: Jacob Jones, 19 years old.
Student: Philip Smith, 19 years old.
Student: Philip Davies, 17 years old.
请注意,尽管我们已将四名学生添加到集合中,但我们只看到其中三名学生的信息被显示。这是因为有两名学生具有完全相同的数据。当比较它们两者的哈希时,它们被发现是相同的,这导致比较这两个对象本身(使用 operator==
,因此它存在于 student
中)。如果比较测试为真,则不添加新对象(与已插入的对象比较相同)。