【C++ STL学习笔记】C+(5),2024年最新Golang开发者该学习哪些东西提高竞争力-程序员宅基地

技术标签: 2024年程序员学习  学习  c++  笔记  

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

//执行此语句会抛出 out_of_range 异常
//cout << umap.at(“GO教程”);
return 0;
}

程序执行结果为:

http://c.biancheng.net/python/

此程序中,第 13 行代码用于获取 umap 容器中键为“Python教程”对应的值,由于 umap 容器确实有符合条件的键值对,因此可以成功执行;而第 17 行代码,由于当前 umap 容器没有存储以“Go教程”为键的键值对,因此执行此语句会抛出 out_of_range 异常。

  1. 运算符和 at() 成员方法基本能满足大多数场景的需要。除此之外,还可以借助 unordered_map 模板中提供的 find() 成员方法。

和前面方法不同的是,通过 find() 方法得到的是一个正向迭代器,该迭代器的指向分以下 2 种情况:

  1. 当 find() 方法成功找到以指定元素作为键的键值对时,其返回的迭代器就指向该键值对;
  2. 当 find() 方法查找失败时,其返回的迭代器和 end() 方法返回的迭代器一样,指向容器中最后一个键值对之后的位置。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”},
{“Linux教程”,“http://c.biancheng.net/linux/”} };
//查找成功
unordered_map<string, string>::iterator iter = umap.find(“Python教程”);
cout << iter->first << " " << iter->second << endl;
//查找失败
unordered_map<string, string>::iterator iter2 = umap.find(“GO教程”);
if (iter2 == umap.end()) {
cout << “当前容器中没有以"GO教程"为键的键值对”;
}
return 0;
}

程序执行结果为:

Python教程 http://c.biancheng.net/python/
当前容器中没有以"GO教程"为键的键值对

  1. 除了 find() 成员方法之外,甚至可以借助 begin()/end() 或者 cbegin()/cend(),通过遍历整个容器中的键值对来找到目标键值对。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”},
{“Linux教程”,“http://c.biancheng.net/linux/”} };
//遍历整个容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
//判断当前的键值对是否就是要找的
if (!iter->first.compare(“Java教程”)) {
cout << iter->second << endl;
break;
}
}
return 0;
}

程序执行结果为:

http://c.biancheng.net/java/

以上 4 种方法中,前 2 种方法基本能满足多数场景的需要,建议初学者首选 at() 成员方法!

C++ unordered_map insert()用法精讲

为了方便用户向已建 unordered_map 容器中添加新的键值对,该容器模板中提供了 insert() 方法,本节就对此方法的用法做详细的讲解。

unordered_map 模板类中,提供了多种语法格式的 insert() 方法,根据功能的不同,可划分为以下几种用法。

  1. insert() 方法可以将 pair 类型的键值对元素添加到 unordered_map 容器中,其语法格式有 2 种:

//以普通方式传递参数
pair<iterator,bool> insert ( const value_type& val );
//以右值引用的方式传递参数
template
pair<iterator,bool> insert ( P&& val );

有关右值引用,可阅读《C++右值引用详解》一文,这里不再做具体解释。

以上 2 种格式中,参数 val 表示要添加到容器中的目标键值对元素;该方法的返回值为 pair类型值,内部包含一个 iterator 迭代器和 bool 变量:

  • 当 insert() 将 val 成功添加到容器中时,返回的迭代器指向新添加的键值对,bool 值为 True;
  • 当 insert() 添加键值对失败时,意味着当前容器中本就存储有和要添加键值对的键相等的键值对,这种情况下,返回的迭代器将指向这个导致插入操作失败的迭代器,bool 值为 False。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map<string, string> umap;
//构建要添加的键值对
std::pair<string, string>mypair(“STL教程”, “http://c.biancheng.net/stl/”);
//创建接收 insert() 方法返回值的pair类型变量
std::pair<unordered_map<string, string>::iterator, bool> ret;
//调用 insert() 方法的第一种语法格式
ret = umap.insert(mypair);
cout << "bool = " << ret.second << endl;
cout << “iter -> " << ret.first->first <<” " << ret.first->second << endl;

//调用 insert() 方法的第二种语法格式
ret = umap.insert(std::make_pair(“Python教程”,“http://c.biancheng.net/python/”));
cout << "bool = " << ret.second << endl;
cout << "iter -> " << ret.first->first << " " << ret.first->second << endl;
return 0;
}

程序执行结果为:

bool = 1
iter -> STL教程 http://c.biancheng.net/stl/
bool = 1
iter -> Python教程 http://c.biancheng.net/python/

从输出结果很容易看出,两次添加键值对的操作,insert() 方法返回值中的 bool 变量都为 1,表示添加成功,此时返回的迭代器指向的是添加成功的键值对。

  1. 除此之外,insert() 方法还可以指定新键值对要添加到容器中的位置,其语法格式如下:

//以普通方式传递 val 参数
iterator insert ( const_iterator hint, const value_type& val );
//以右值引用方法传递 val 参数
template
iterator insert ( const_iterator hint, P&& val );

以上 2 种语法格式中,hint 参数为迭代器,用于指定新键值对要添加到容器中的位置;val 参数指的是要添加容器中的键值对;方法的返回值为迭代器:

  • 如果 insert() 方法成功添加键值对,该迭代器指向新添加的键值对;
  • 如果 insert() 方法添加键值对失败,则表示容器中本就包含有相同键的键值对,该方法返回的迭代器就指向容器中键相同的键值对;

注意,以上 2 种语法格式中,虽然通过 hint 参数指定了新键值对添加到容器中的位置,但该键值对真正存储的位置,并不是 hint 参数说了算,最终的存储位置仍取决于该键值对的键的值。

举个例子:
#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map<string, string> umap;
//构建要添加的键值对
std::pair<string, string>mypair(“STL教程”, “http://c.biancheng.net/stl/”);
//创建接收 insert() 方法返回值的迭代器类型变量
unordered_map<string, string>::iterator iter;
//调用第一种语法格式
iter = umap.insert(umap.begin(), mypair);
cout << “iter -> " << iter->first <<” " << iter->second << endl;

//调用第二种语法格式
iter = umap.insert(umap.begin(),std::make_pair(“Python教程”, “http://c.biancheng.net/python/”));
cout << "iter -> " << iter->first << " " << iter->second << endl;
return 0;
}

程序输出结果为:

iter -> STL教程 http://c.biancheng.net/stl/
iter -> Python教程 http://c.biancheng.net/python/

  1. insert() 方法还支持将某一个 unordered_map 容器中指定区域内的所有键值对,复制到另一个 unordered_map 容器中,其语法格式如下:

template
void insert ( InputIterator first, InputIterator last );

其中 first 和 last 都为迭代器,[first, last)表示复制其它 unordered_map 容器中键值对的区域。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建并初始化 umap 容器
unordered_map<string, string> umap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
//创建一个空的 unordered_map 容器
unordered_map<string, string> otherumap;
//指定要拷贝 umap 容器中键值对的范围
unordered_map<string, string>::iterator first = ++umap.begin();
unordered_map<string, string>::iterator last = umap.end();
//将指定 umap 容器中 [first,last) 区域内的键值对复制给 otherumap 容器
otherumap.insert(first, last);
//遍历 otherumap 容器中存储的键值对
for (auto iter = otherumap.begin(); iter != otherumap.end(); ++iter){
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

程序输出结果为:

Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/

  1. 除了以上 3 种方式,insert() 方法还支持一次向 unordered_map 容器添加多个键值对,其语法格式如下:

void insert ( initializer_list<value_type> il );

其中,il 参数指的是可以用初始化列表的形式指定多个键值对元素。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建空的 umap 容器
unordered_map<string, string> umap;
//向 umap 容器同时添加多个键值对
umap.insert({ {“STL教程”,“http://c.biancheng.net/stl/”},
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”} });
//遍历输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter){
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

程序输出结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/

总的来说,unordered_map 模板类提供的 insert() 方法,有以上 4 种用法,读者可以根据实际场景的需要自行选择使用哪一种。

C++ STL unordered_map删除元素:erase()和clear()

C++ STL 标准库为了方便用户可以随时删除 unordered_map 容器中存储的键值对,unordered_map 容器类模板中提供了以下 2 个成员方法:

  • erase():删除 unordered_map 容器中指定的键值对;
  • clear():删除 unordered_map 容器中所有的键值对,即清空容器。

本节就对以上 2 个成员方法的用法做详细的讲解。

unordered_map erase()方法

为了满足不同场景删除 unordered_map 容器中键值对的需要,此容器的类模板中提供了 3 种语法格式的 erase() 方法。

  1. erase() 方法可以接受一个正向迭代器,并删除该迭代器指向的键值对。该方法的语法格式如下:

iterator erase ( const_iterator position );

其中 position 为指向容器中某个键值对的迭代器,该方法会返回一个指向被删除键值对之后位置的迭代器。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“STL教程”, “http://c.biancheng.net/stl/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“Java教程”, “http://c.biancheng.net/java/”} };
//输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
cout << “erase:” << endl;
//定义一个接收 erase() 方法的迭代器
unordered_map<string,string>::iterator ret;
//删除容器中第一个键值对
ret = umap.erase(umap.begin());
//输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
cout << "ret = " << ret->first << " " << ret->second << endl;
return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
erase:
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
ret = Python教程 http://c.biancheng.net/python/

可以看到,通过给 erase() 方法传入指向容器中第一个键值对的迭代器,该方法可以将容器中第一个键值对删除,同时返回一个指向被删除键值对之后位置的迭代器。

注意,如果erase()方法删除的是容器存储的最后一个键值对,则该方法返回的迭代器,将指向容器中最后一个键值对之后的位置(等同于 end() 方法返回的迭代器)。

  1. 我们还可以直接将要删除键值对的键作为参数直接传给 erase() 方法,该方法会自行去 unordered_map 容器中找和给定键相同的键值对,将其删除。erase() 方法的语法格式如下:

size_type erase ( const key_type& k );

其中,k 表示目标键值对的键的值;该方法会返回一个整数,其表示成功删除的键值对的数量。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“STL教程”, “http://c.biancheng.net/stl/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“Java教程”, “http://c.biancheng.net/java/”} };
//输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
int delNum = umap.erase(“Python教程”);
cout << "delNum = " << delNum << endl;
//再次输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
delNum = 1
STL教程 http://c.biancheng.net/stl/
Java教程 http://c.biancheng.net/java/

通过输出结果可以看到,通过将 “Python教程” 传给 erase() 方法,就成功删除了 umap 容器中键为 “Python教程” 的键值对。

  1. 除了支持删除 unordered_map 容器中指定的某个键值对,erase() 方法还支持一次删除指定范围内的所有键值对,其语法格式如下:

iterator erase ( const_iterator first, const_iterator last );

其中 first 和 last 都是正向迭代器,[first, last) 范围内的所有键值对都会被 erase() 方法删除;同时,该方法会返回一个指向被删除的最后一个键值对之后一个位置的迭代器。

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“STL教程”, “http://c.biancheng.net/stl/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“Java教程”, “http://c.biancheng.net/java/”} };
//first 指向第一个键值对
unordered_map<string, string>::iterator first = umap.begin();
//last 指向最后一个键值对
unordered_map<string, string>::iterator last = umap.end();
//删除[fist,last)范围内的键值对
auto ret = umap.erase(first, last);
//输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

执行程序会发现,没有输出任何数据,因为 erase() 方法删除了 umap 容器中 [begin(), end()) 范围内所有的元素。

unordered_map clear()方法

在个别场景中,可能需要一次性删除 unordered_map 容器中存储的所有键值对,可以使用 clear() 方法,其语法格式如下:

void clear()

举个例子:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap{
{“STL教程”, “http://c.biancheng.net/stl/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“Java教程”, “http://c.biancheng.net/java/”} };
//输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
//删除容器内所有键值对
umap.clear();
cout << "umap size = " << umap.size() << endl;
return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
umap size = 0

显然,通过调用 clear() 方法,原本包含 3 个键值对的 umap 容器,变成了空容器。

注意,虽然使用 erase() 方法的第 3 种语法格式,可能实现删除 unordered_map 容器内所有的键值对,但更推荐使用 clear() 方法。

C++ STL unordered_multimap容器精讲

C++ STL 标准库中,除了提供有 unordered_map 无序关联容器,还提供有和 unordered_map 容器非常相似的 unordered_multimap 无序关联容器。

和 unordered_map 容器一样,unordered_multimap 容器也以键值对的形式存储数据,且底层也采用哈希表结构存储各个键值对。两者唯一的不同之处在于,unordered_multimap 容器可以存储多个键相等的键值对,而 unordered_map 容器不行。

《深度剖析C++ STL无序容器底层原理》一文提到,无序容器中存储的各个键值对,都会哈希存到各个桶(本质为链表)中。而对于 unordered_multimap 容器来说,其存储的所有键值对中,键相等的键值对会被哈希到同一个桶中存储。

另外值得一提得是,STL 标准库中实现 unordered_multimap 容器的模板类并没有定义在以自己名称命名的头文件中,而是和 unordered_map 容器一样,定义在<unordered_map>头文件,且位于 std 命名空间中。因此,在使用 unordered_multimap 容器之前,程序中应包含如下 2 行代码:

#include <unordered_map>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则后续程序中在使用此容器时,需手动注明 std 命名空间(强烈建议初学者使用)。

unordered_multimap 容器模板的定义如下所示:

template < class Key, //键(key)的类型
class T, //值(value)的类型
class Hash = hash, //底层存储键值对时采用的哈希函数
class Pred = equal_to, //判断各个键值对的键相等的规则
class Alloc = allocator< pair<const Key,T> > // 指定分配器对象的类型

class unordered_multimap;

以上 5 个参数中,必须显式给前 2 个参数传值,且除极个别的情况外,最多只使用前 4 个参数,它们各自的含义和功能如表 1 所示。

参数 含义
<key,T> 前 2 个参数分别用于确定键值对中键和值的类型,也就是存储键值对的类型。
Hash = hash 用于指明容器在存储各个键值对时要使用的哈希函数,默认使用 STL 标准库提供的 hash 哈希函数。注意,默认哈希函数只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to unordered_multimap 容器可以存储多个键相等的键值对,而判断是否相等的规则,由此参数指定。默认情况下,使用 STL 标准库中提供的 equal_to 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

注意,当 unordered_multimap 容器中存储键值对的键为自定义类型时,默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,这种情况下,需要我们自定义适用的哈希函数和比较函数,并分别显式传递给 Hash 参数和 Pred 参数。

关于给 unordered_multimap 容器自定义哈希函数和比较函数的方法,后续章节会做详细讲解。

创建C++ unordered_multimap容器的方法

常见的创建 unordered_map 容器的方法有以下几种。

  1. 利用 unordered_multimap 容器类模板中的默认构造函数,可以创建空的 unordered_multimap 容器。比如:

std::unordered_multimap<std::string, std::string>myummap;

如果程序中已经默认指定了 std 命令空间,这里可以省略所有的 std::。

由此,就创建好了一个可存储 <string, string> 类型键值对的 unordered_multimap 容器,只不过当前容器是空的,即没有存储任何键值对。

  1. 当然,在创建空 unordered_multimap 容器的基础上,可以完成初始化操作。比如:

unordered_multimap<string, string>myummap{
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”},
{“Linux教程”,“http://c.biancheng.net/linux/”} };

通过此方法创建的 myummap 容器中,就包含有 3 个键值对。

  1. 另外,unordered_multimap 模板中还提供有复制(拷贝)构造函数,可以实现在创建 unordered_multimap 容器的基础上,用另一 unordered_multimap 容器中的键值对为其初始化。

例如,在第二种方式创建好 myummap 容器的基础上,再创建并初始化一个 myummap2 容器:

unordered_multimap<string, string>myummap2(myummap);

由此,刚刚创建好的 myummap2 容器中,就包含有 myummap 容器中所有的键值对。

除此之外,C++ 11 标准中还向 unordered_multimap 模板类增加了移动构造函数,即以右值引用的方式将临时 unordered_multimap 容器中存储的所有键值对,全部复制给新建容器。例如:

//返回临时 unordered_multimap 容器的函数
std::unordered_multimap <std::string, std::string > retUmmap() {
std::unordered_multimap<std::string, std::string>tempummap{
{“Python教程”,“http://c.biancheng.net/python/”},
{“Java教程”,“http://c.biancheng.net/java/”},
{“Linux教程”,“http://c.biancheng.net/linux/”} };
return tempummap;
}
//创建并初始化 myummap 容器
std::unordered_multimap<std::string, std::string> myummap(retummap());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_multimap 类模板提供的迭代器,在现有 unordered_multimap 容器中选择部分区域内的键值对,为新建 unordered_multimap 容器初始化。例如:

//传入 2 个迭代器,
std::unordered_multimap<std::string, std::string> myummap2(++myummap.begin(), myummap.end());

通过此方式创建的 myummap2 容器,其内部就包含 myummap 容器中除第 1 个键值对外的所有其它键值对。

C++ unordered_multimap容器的成员方法

和 unordered_map 容器相比,unordered_multimap 容器的类模板中没有重载 [ ] 运算符,也没有提供 at() 成员方法,除此之外它们完全一致。

没有提供 [ ] 运算符和 at() 成员方法,意味着 unordered_multimap 容器无法通过指定键获取该键对应的值,因为该容器允许存储多个键相等的键值对,每个指定的键可能对应多个不同的值。

unordered_multimap 类模板提供的成员方法如表 2 所示。

成员方法 功能
begin() 返回指向容器中第一个键值对的正向迭代器。
end() 返回指向容器中最后一个键值对之后位置的正向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前容器中存有键值对的个数。
max_size() 返回容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
find(key) 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key) 在容器中查找以 key 键的键值对的个数。
equal_range(key) 返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。
emplace() 向容器中添加新键值对,效率比 insert() 方法高。
emplace_hint() 向容器中添加新键值对,效率比 insert() 方法高。
insert() 向容器中添加新键值对。
erase() 删除指定键值对。
clear() 清空容器,即删除容器中存储的所有键值对。
swap() 交换 2 个 unordered_multimap 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。
bucket_count() 返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count() 返回当前系统中,unordered_multimap 容器底层最多可以使用多少桶。
bucket_size(n) 返回第 n 个桶中存储键值对的数量。
bucket(key) 返回以 key 为键的键值对所在桶的编号。
load_factor() 返回 unordered_multimap 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor() 返回或者设置当前 unordered_multimap 容器的负载因子。
rehash(n) 将当前容器底层使用桶的数量设置为 n。
reserve() 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function() 返回当前容器使用的哈希函数对象。

注意,对于实现互换 2 个相同类型 unordered_multimap 容器的键值对,除了可以调用该容器模板类中提供的 swap() 成员方法外,STL 标准库还提供了同名的 swap() 非成员函数。

下面的样例演示了表 2 中部分成员方法的用法:

#include
#include
#include <unordered_map>
using namespace std;
int main()
{
//创建空容器
std::unordered_multimap<std::string, std::string> myummap;
//向空容器中连续添加 5 个键值对
myummap.emplace(“Python教程”, “http://c.biancheng.net/python/”);
myummap.emplace(“STL教程”, “http://c.biancheng.net/stl/”);
myummap.emplace(“Java教程”, “http://c.biancheng.net/java/”);
myummap.emplace(“C教程”, “http://c.biancheng.net”);
myummap.emplace(“C教程”, “http://c.biancheng.net/c/”);
//输出 muummap 容器存储键值对的个数
cout << "myummmap size = " << myummap.size() << endl;
//利用迭代器输出容器中存储的所有键值对
for (auto iter = myummap.begin(); iter != myummap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

程序执行结果为:

myummmap size = 5
Python教程 http://c.biancheng.net/python/
C教程 http://c.biancheng.net
C教程 http://c.biancheng.net/c/
STL教程 http://c.biancheng.net/stl/
Java教程 http://c.biancheng.net/java/

值得一提的是,unordered_multimap 模板提供的所有成员方法的用法,都和 unordered_map 提供的同名成员方法的用法完全相同(仅是调用者发生了改变),由于在讲解 unordered_map 容器时,已经对大部分成员方法的用法做了详细的讲解,后续不再做重复性地赘述。

C++ STL unordered_set容器完全攻略

我们知道,C++ 11 为 STL 标准库增添了 4 种无序(哈希)容器,前面已经对 unordered_map 和 unordered_multimap 容器做了详细的介绍,本节再讲解一种无序容器,即 unordered_set 容器。

unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。

总的来说,unordered_set 容器具有以下几个特性:

  1. 不再以键值对的形式存储数据,而是直接存储数据的值;
  2. 容器内部存储的各个元素的值都互不相等,且不能被修改。
  3. 不会对内部存储的数据进行排序(这和该容器底层采用哈希表结构存储数据有关,可阅读《C++ STL无序容器底层实现原理》一文做详细了解);

对于 unordered_set 容器不以键值对的形式存储数据,读者也可以这样认为,即 unordered_set 存储的都是键和值相等的键值对,为了节省存储空间,该类容器在实际存储时选择只存储每个键值对的值。

另外,实现 unordered_set 容器的模板类定义在<unordered_set>头文件,并位于 std 命名空间中。这意味着,如果程序中需要使用该类型容器,则首先应该包含如下代码:

#include <unordered_set>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则程序中只要用到该容器时,必须手动注明 std 命名空间(强烈建议初学者使用)。

unordered_set 容器的类模板定义如下:

template < class Key, //容器中存储元素的类型
class Hash = hash, //确定元素存储位置所用的哈希函数
class Pred = equal_to, //判断各个元素是否相等所用的函数
class Alloc = allocator //指定分配器对象的类型

class unordered_set;

可以看到,以上 4 个参数中,只有第一个参数没有默认值,这意味着如果我们想创建一个 unordered_set 容器,至少需要手动传递 1 个参数。事实上,在 99% 的实际场景中最多只需要使用前 3 个参数(各自含义如表 1 所示),最后一个参数保持默认值即可。

参数 含义
Key 确定容器存储元素的类型,如果读者将 unordered_set 看做是存储键和值相同的键值对的容器,则此参数则用于确定各个键值对的键和值的类型,因为它们是完全相同的,因此一定是同一数据类型的数据。
Hash = hash 指定 unordered_set 容器底层存储各个元素时,所使用的哈希函数。需要注意的是,默认哈希函数 hash 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to unordered_set 容器内部不能存储相等的元素,而衡量 2 个元素是否相等的标准,取决于该参数指定的函数。 默认情况下,使用 STL 标准库中提供的 equal_to 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

注意,如果 unordered_set 容器中存储的元素为自定义的数据类型,则默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。至于如何实现自定义,后续章节会做详细讲解。

创建C++ unordered_set容器

前面介绍了如何创建 unordered_map 和 unordered_multimap 容器,值得一提的是,创建它们的所有方式完全适用于 unordereded_set 容器。不过,考虑到一些读者可能尚未学习其它无序容器,因此这里还是讲解一下创建 unordered_set 容器的几种方法。

  1. 通过调用 unordered_set 模板类的默认构造函数,可以创建空的 unordered_set 容器。比如:

std::unordered_setstd::string uset;

如果程序已经引入了 std 命名空间,这里可以省略所有的 std::。

由此,就创建好了一个可存储 string 类型值的 unordered_set 容器,该容器底层采用默认的哈希函数 hash 和比较函数 equal_to。

  1. 当然,在创建 unordered_set 容器的同时,可以完成初始化操作。比如:

std::unordered_setstd::string uset{ “http://c.biancheng.net/c/”,
“http://c.biancheng.net/java/”,
“http://c.biancheng.net/linux/” };

通过此方法创建的 uset 容器中,就包含有 3 个 string 类型元素。

  1. 还可以调用 unordered_set 模板中提供的复制(拷贝)构造函数,将现有 unordered_set 容器中存储的元素全部用于为新建 unordered_set 容器初始化。

例如,在第二种方式创建好 uset 容器的基础上,再创建并初始化一个 uset2 容器:

std::unordered_setstd::string uset2(uset);

由此,umap2 容器中就包含有 umap 容器中所有的元素。

除此之外,C++ 11 标准中还向 unordered_set 模板类增加了移动构造函数,即以右值引用的方式,利用临时 unordered_set 容器中存储的所有元素,给新建容器初始化。例如:

//返回临时 unordered_set 容器的函数
std::unordered_set std::string retuset() {
std::unordered_setstd::string tempuset{ “http://c.biancheng.net/c/”,
“http://c.biancheng.net/java/”,
“http://c.biancheng.net/linux/” };
return tempuset;
}
//调用移动构造函数,创建 uset 容器
std::unordered_setstd::string uset(retuset());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_set 类模板提供的迭代器,在现有 unordered_set 容器中选择部分区域内的元素,为新建 unordered_set 容器初始化。例如:

//传入 2 个迭代器,
std::unordered_setstd::string uset2(++uset.begin(),uset.end());

通过此方式创建的 uset2 容器,其内部就包含 uset 容器中除第 1 个元素外的所有其它元素。

C++ unordered_set容器的成员方法

unordered_set 类模板中,提供了如表 2 所示的成员方法。

成员方法 功能
begin() 返回指向容器中第一个元素的正向迭代器。
end(); 返回指向容器中最后一个元素之后位置的正向迭代器。
cbegin() 和 begin() 功能相同,只不过其返回的是 const 类型的正向迭代器。
cend() 和 end() 功能相同,只不过其返回的是 const 类型的正向迭代器。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前容器中存有元素的个数。
max_size() 返回容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
find(key) 查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key) 在容器中查找值为 key 的元素的个数。
equal_range(key) 返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中值为 key 的元素所在的范围。
emplace() 向容器中添加新元素,效率比 insert() 方法高。
emplace_hint() 向容器中添加新元素,效率比 insert() 方法高。
insert() 向容器中添加新元素。
erase() 删除指定元素。
clear() 清空容器,即删除容器中存储的所有元素。
swap() 交换 2 个 unordered_map 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等。
bucket_count() 返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count() 返回当前系统中,unordered_map 容器底层最多可以使用多少桶。
bucket_size(n) 返回第 n 个桶中存储元素的数量。
bucket(key) 返回值为 key 的元素所在桶的编号。
load_factor() 返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor() 返回或者设置当前 unordered_map 容器的负载因子。
rehash(n) 将当前容器底层使用桶的数量设置为 n。
reserve() 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function() 返回当前容器使用的哈希函数对象。

注意,此容器模板类中没有重载 [ ] 运算符,也没有提供 at() 成员方法。不仅如此,由于 unordered_set 容器内部存储的元素值不能被修改,因此无论使用那个迭代器方法获得的迭代器,都不能用于修改容器中元素的值。

另外,对于实现互换 2 个相同类型 unordered_set 容器的所有元素,除了调用表 2 中的 swap() 成员方法外,还可以使用 STL 标准库提供的 swap() 非成员函数,它们具有相同的名称,用法也相同(都只需要传入 2 个参数即可),仅是调用方式上有差别。

下面的样例演示了表 2 中部分成员方法的用法:

#include
#include
#include <unordered_set>
using namespace std;
int main()
{
//创建一个空的unordered_set容器
std::unordered_setstd::string uset;
//给 uset 容器添加数据
uset.emplace(“http://c.biancheng.net/java/”);
uset.emplace(“http://c.biancheng.net/c/”);
uset.emplace(“http://c.biancheng.net/python/”);
//查看当前 uset 容器存储元素的个数
cout << "uset size = " << uset.size() << endl;
//遍历输出 uset 容器存储的所有元素
for (auto iter = uset.begin(); iter != uset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

程序执行结果为:

uset size = 3
http://c.biancheng.net/java/
http://c.biancheng.net/c/
http://c.biancheng.net/python/

注意,表 2 中绝大多数成员方法的用法,都和 unordered_map 容器提供的同名成员方法相同,读者可翻阅前面的文章做详细了解,当然也可以到 C++
STL标准库官网
查询。

C++ STL unordered_multiset容器详解

前面章节详细地介绍了 unordered_set 容器的特定和用法,在此基础上,本节再介绍一个类似的 C++ STL 无序容器,即 unordered_multiset 容器。

所谓“类似”,指的是 unordered_multiset 容器大部分的特性都和 unordered_set 容器相同,包括:

  1. unordered_multiset 不以键值对的形式存储数据,而是直接存储数据的值;
  2. 该类型容器底层采用的也是哈希表存储结构(可阅读《C++ STL无序容器底层实现原理》一文做详细了解),它不会对内部存储的数据进行排序;
  3. unordered_multiset 容器内部存储的元素,其值不能被修改。

和 unordered_set 容器不同的是,unordered_multiset 容器可以同时存储多个值相同的元素,且这些元素会存储到哈希表中同一个桶(本质就是链表)上。

读者可以这样认为,unordered_multiset 除了能存储相同值的元素外,它和 unordered_set 容器完全相同。

另外值得一提的是,实现 unordered_multiset 容器的模板类并没有定义在以该容器名命名的文件中,而是和 unordered_set 容器共用同一个<unordered_set>头文件,并且也位于 std 命名空间。因此,如果程序中需要使用该类型容器,应包含如下代码:

#include <unordered_set>using namespace std;

注意,第二行代码不是必需的,但如果不用,则程序中只要用到该容器时,必须手动注明 std 命名空间(强烈建议初学者使用)。

unordered_multiset 容器类模板的定义如下:

template < class Key, //容器中存储元素的类型
class Hash = hash, //确定元素存储位置所用的哈希函数
class Pred = equal_to, //判断各个元素是否相等所用的函数
class Alloc = allocator //指定分配器对象的类型

class unordered_multiset;

需要说明的是,在 99% 的实际场景中,最多只需要使用前 3 个参数(各自含义如表 1 所示),最后一个参数保持默认值即可。

参数 含义
Key 确定容器存储元素的类型,如果读者将 unordered_multiset 看做是存储键和值相同的键值对的容器,则此参数则用于确定各个键值对的键和值的类型,因为它们是完全相同的,因此一定是同一数据类型的数据。
Hash = hash 指定 unordered_multiset 容器底层存储各个元素时所使用的哈希函数。需要注意的是,默认哈希函数 hash 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to 用于指定 unordered_multiset 容器判断元素值相等的规则。默认情况下,使用 STL 标准库中提供的 equal_to 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

总之,如果 unordered_multiset 容器中存储的元素为自定义的数据类型,则默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。至于如何实现自定义,后续章节会做详细讲解。

创建C++ unordered_multiset容器

考虑到不同场景的需要,unordered_multiset 容器模板类共提供了以下 4 种创建 unordered_multiset 容器的方式。

  1. 调用 unordered_multiset 模板类的默认构造函数,可以创建空的 unordered_multiset 容器。比如:

std::unordered_multisetstd::string umset;

如果程序已经引入了 std 命名空间,这里可以省略所有的 std::。

由此,就创建好了一个可存储 string 类型值的 unordered_multiset 容器,该容器底层采用默认的哈希函数 hash 和比较函数 equal_to。

  1. 当然,在创建 unordered_multiset 容器的同时,可以进行初始化操作。比如:

std::unordered_multisetstd::string umset{ “http://c.biancheng.net/c/”,
“http://c.biancheng.net/java/”,
“http://c.biancheng.net/linux/” };

通过此方法创建的 umset 容器中,内部存有 3 个 string 类型元素。

  1. 还可以调用 unordered_multiset 模板中提供的复制(拷贝)构造函数,将现有 unordered_multiset 容器中存储的元素全部用于为新建 unordered_multiset 容器初始化。

例如,在第二种方式创建好 umset 容器的基础上,再创建并初始化一个 umset2 容器:

std::unordered_multisetstd::string umset2(umset);

由此,umap2 容器中就包含有 umap 容器中所有的元素。

除此之外,C++ 11 标准中还向 unordered_multiset 模板类增加了移动构造函数,即以右值引用的方式,利用临时 unordered_multiset 容器中存储的所有元素,给新建容器初始化。例如:

//返回临时 unordered_multiset 容器的函数
std::unordered_multiset std::string retumset() {
std::unordered_multisetstd::string tempumset{ “http://c.biancheng.net/c/”,
“http://c.biancheng.net/java/”,
“http://c.biancheng.net/linux/” };
return tempumset;
}
//调用移动构造函数,创建 umset 容器
std::unordered_multisetstd::string umset(retumset());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_multiset 类模板提供的迭代器,在现有 unordered_multiset 容器中选择部分区域内的元素,为新建 unordered_multiset 容器初始化。例如:

//传入 2 个迭代器,
std::unordered_multisetstd::string umset2(++umset.begin(), umset.end());

通过此方式创建的 umset2 容器,其内部就包含 umset 容器中除第 1 个元素外的所有其它元素。

C++ unordered_multimap容器的成员方法

值得一提的是,unordered_multiset 模板类中提供的成员方法,无论是种类还是数量,都和 unordered_set 类模板一样,如表 2 所示。

成员方法 功能
begin() 返回指向容器中第一个元素的正向迭代器。
end(); 返回指向容器中最后一个元素之后位置的正向迭代器。
cbegin() 和 begin() 功能相同,只不过其返回的是 const 类型的正向迭代器。
cend() 和 end() 功能相同,只不过其返回的是 const 类型的正向迭代器。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前容器中存有元素的个数。
max_size() 返回容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
find(key) 查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key) 在容器中查找值为 key 的元素的个数。
equal_range(key) 返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中值为 key 的元素所在的范围。
emplace() 向容器中添加新元素,效率比 insert() 方法高。
emplace_hint() 向容器中添加新元素,效率比 insert() 方法高。
insert() 向容器中添加新元素。
erase() 删除指定元素。
clear() 清空容器,即删除容器中存储的所有元素。
swap() 交换 2 个 unordered_multimap 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等。
bucket_count() 返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count() 返回当前系统中,容器底层最多可以使用多少桶。
bucket_size(n) 返回第 n 个桶中存储元素的数量。
bucket(key) 返回值为 key 的元素所在桶的编号。
load_factor() 返回容器当前的负载因子。所谓负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor() 返回或者设置当前 unordered_map 容器的负载因子。
rehash(n) 将当前容器底层使用桶的数量设置为 n。
reserve() 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function() 返回当前容器使用的哈希函数对象。

注意,和 unordered_set 容器一样,unordered_multiset 模板类也没有重载 [ ] 运算符,没有提供 at() 成员方法。不仅如此,无论是由哪个成员方法返回的迭代器,都不能用于修改容器中元素的值。

另外,对于互换 2 个相同类型 unordered_multiset 容器存储的所有元素,除了调用表 2 中的 swap() 成员方法外,STL 标准库也提供了 swap() 非成员函数。

下面的样例演示了表 2 中部分成员方法的用法:

#include
#include
#include <unordered_set>
using namespace std;
int main()
{
//创建一个空的unordered_multiset容器
std::unordered_multisetstd::string umset;
//给 uset 容器添加数据
umset.emplace(“http://c.biancheng.net/java/”);
umset.emplace(“http://c.biancheng.net/c/”);
umset.emplace(“http://c.biancheng.net/python/”);
umset.emplace(“http://c.biancheng.net/c/”);
//查看当前 umset 容器存储元素的个数
cout << "umset size = " << umset.size() << endl;
//遍历输出 umset 容器存储的所有元素
for (auto iter = umset.begin(); iter != umset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

程序执行结果为:

umset size = 4
http://c.biancheng.net/java/
http://c.biancheng.net/c/
http://c.biancheng.net/c/
http://c.biancheng.net/python/

注意,表 2 中绝大多数成员方法的用法,都和 unordered_map 容器提供的同名成员方法相同,读者可翻阅前面的文章做详细了解,当然也可以到C++ STL标准库官网查询。

如何自定义C++ STL无序容器的哈希函数和比较规则?(超级详细)

前面在讲解 unordered_map、unordered_multimap、unordered_set 以及 unordered_multiset 这 4 种无序关联式容器(哈希容器)时,遗留过一个共性问题,即如何给无序容器自定义一个哈希函数和比较规则?

注意,虽然每种无序容器都指定了默认的 hash 哈希函数和 equal_to 比较规则,但它们仅适用于存储基本类型(比如 int、double、float、string 等)数据的无序容器。换句话说,如果无序容器存储的数据类型为自定义的结构体或类,则 STL 标准库提供的 hash 和 equal_to 将不再适用。

C++无序容器自定义哈希函数

我们知道,无序容器以键值对的方式存储数据(unordered_set 和 unordered_multiset 容器可以看做存储的是键和值相等的键值对),且底层采用哈希表结构存储各个键值对。在此存储结构中,哈希函数的功能是根据各个键值对中键的值,计算出一个哈希值(本质就是一个整数),哈希表可以根据该值判断出该键值对具体的存储位置。

简单地理解哈希函数,它可以接收一个元素,并通过内部对该元素做再加工,最终会得出一个整形值并反馈回来。需要注意的是,哈希函数只是一个称谓,其本体并不是普通的函数形式,而是一个函数对象类。因此,如果我们想自定义个哈希函数,就需要自定义一个函数对象类。

关于什么函数对象类,可阅读《C++函数对象详解》一节做详细了解,由于不是本节重点,这里不再赘述。

举个例子,假设有如下一个 Person 类:

class Person {
public:
Person(string name, int age) :name(name), age(age) {};
string getName() const;
int getAge() const;
private:
string name;
int age;
};
string Person::getName() const {
return this->name;
}
int Person::getAge() const {
return this->age;
}

在此基础上,假设我们想创建一个可存储 Person 类对象的 unordered_set 容器,考虑到 Person 为自定义的类型,因此默认的 hash 哈希函数不再适用,这时就需要以函数对象类的方式自定义一个哈希函数。比如:

class hash_fun {
public:
int operator()(const Person &A) const {
return A.getAge();
}
};

注意,重载 ( ) 运算符时,其参数必须为 const 类型,且该方法也必须用 const 修饰。

可以看到,我们利用 hash_fun 函数对象类的 ( ) 运算符重载方法,自定义了适用于 Person 类对象的哈希函数。该哈希函数每接收一个 Person 类对象,都会返回该对象的 age 成员变量的值。

事实上,默认的 hash 哈希函数,其底层也是以函数对象类的形式实现的。

由此,在创建存储 Person 类对象的 unordered_set 容器时,可以将 hash_fun 作为参数传递给该容器模板类中的 Pred 参数:

std::unordered_set<Person, hash_fun> myset;

但是,此时创建的 myset 容器还无法使用,因为该容器使用的是默认的 std::equal_to 比较规则,但此规则并不适用于该容器。

C++无序容器自定义比较规则

和哈希函数一样,无论创建哪种无序容器,都需要为其指定一种可比较容器中各个元素是否相等的规则。

值得一提的是,默认情况下无序容器使用的 std::equal_to 比较规则,其本质也是一个函数对象类,底层实现如下:

template
class equal_to
{
public:
bool operator()(const T& _Left, const T& _Right) const{
return (_Left == _Right);
}
};

可以看到,该规则在底层实现过程中,直接用 == 运算符比较容器中任意 2 个元素是否相等,这意味着,如果容器中存储的元素类型,支持直接用 == 运算符比较是否相等,则该容器可以使用默认的 std::equal_to 比较规则;反之,就不可以使用。

显然,对于我们上面创建的 myset 容器,其内部存储的是 Person 类对象,不支持直接使用 == 运算符做比较。这种情况下,有以下 2 种方式可以解决此问题:

  1. 在 Person 类中重载 == 运算符,这会使得 std::equal_to 比较规则中使用的 == 运算符变得合法,myset 容器就可以继续使用 std::equal_to 比较规则;
  2. 以函数对象类的方式,自定义一个适用于 myset 容器的比较规则。
1) 重载==运算符

如果选用第一种解决方式,仍以 Python 类为例,在此类的外部添加如下语句:

bool operator==(const Person &A, const Person &B) {
return (A.getAge() == B.getAge());
}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
重载 ( ) 运算符时,其参数必须为 const 类型,且该方法也必须用 const 修饰。

可以看到,我们利用 hash_fun 函数对象类的 ( ) 运算符重载方法,自定义了适用于 Person 类对象的哈希函数。该哈希函数每接收一个 Person 类对象,都会返回该对象的 age 成员变量的值。

事实上,默认的 hash 哈希函数,其底层也是以函数对象类的形式实现的。

由此,在创建存储 Person 类对象的 unordered_set 容器时,可以将 hash_fun 作为参数传递给该容器模板类中的 Pred 参数:

std::unordered_set<Person, hash_fun> myset;

但是,此时创建的 myset 容器还无法使用,因为该容器使用的是默认的 std::equal_to 比较规则,但此规则并不适用于该容器。

C++无序容器自定义比较规则

和哈希函数一样,无论创建哪种无序容器,都需要为其指定一种可比较容器中各个元素是否相等的规则。

值得一提的是,默认情况下无序容器使用的 std::equal_to 比较规则,其本质也是一个函数对象类,底层实现如下:

template
class equal_to
{
public:
bool operator()(const T& _Left, const T& _Right) const{
return (_Left == _Right);
}
};

可以看到,该规则在底层实现过程中,直接用 == 运算符比较容器中任意 2 个元素是否相等,这意味着,如果容器中存储的元素类型,支持直接用 == 运算符比较是否相等,则该容器可以使用默认的 std::equal_to 比较规则;反之,就不可以使用。

显然,对于我们上面创建的 myset 容器,其内部存储的是 Person 类对象,不支持直接使用 == 运算符做比较。这种情况下,有以下 2 种方式可以解决此问题:

  1. 在 Person 类中重载 == 运算符,这会使得 std::equal_to 比较规则中使用的 == 运算符变得合法,myset 容器就可以继续使用 std::equal_to 比较规则;
  2. 以函数对象类的方式,自定义一个适用于 myset 容器的比较规则。
1) 重载==运算符

如果选用第一种解决方式,仍以 Python 类为例,在此类的外部添加如下语句:

bool operator==(const Person &A, const Person &B) {
return (A.getAge() == B.getAge());
}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-LjSNTmuS-1713433624916)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/2401_84263403/article/details/137930584

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签