上面已经描述了四种无序关联容器(散列容器):unordered_map、unordered_multimap、unordered_set和unordered_multiset
请注意,每个无序容器都指定了默认散列函数和equal_to比较规则,但仅适用于存储基本类型数据的无序容器,如int、double、float和string。 这意味着,如果存储在无序容器中的数据类型是自定义结构或类,则不再应用STL标准库提供的hash和equal_to。
C无序容器的自定义散列函数
可以假设无序的容器包含键-值对的数据,而unordered_set和unordered_multiset容器包含键-值相等的键-值对。 每个键/值对都以哈希表结构存储。 在该存储结构中,散列函数的功能可以根据每个键值对中键的值计算散列值(本质上是整数),散列表可以根据该值确定该键值对的具体存储位置。
简单了解散列函数后,通过接收元素并在内部对该元素进行再加工,最终得到并反馈整形值。 请注意,散列函数只是一个名称,其主体不是普通的函数格式,而是函数对象类。 因此,如果要定制散列函数,则必须定制函数对象类。
关于什么样的函数对象类,可以通过阅读《C++函数对象详解》一节来详细了解,但由于不是本节的重点,因此在此省略说明。
例如,假设您有以下人员类:
类人员{
公共:
人员(string name,int age ) :name ) name ),age ) {};
字符串获取名称() const;
输入获取代理() const;
私有:
字符串名称;
int age;
(;
字符串个人: getname () const { )
return this-name;
}
输入人员: get age () const { )
返回this-age;
}
假设您要创建一个可以包含Person类对象的unordered_set容器。 考虑到人员是自定义类型,因此不再应用默认散列函数,必须将散列函数定制为函数对象类。 例如:
class hash_fun {
公共:
int operator () ) ) const Person A ) const {
return A.getAge (;
}
(;
请注意,重载运算符()时,参数为const类型,并且方法也必须用const限定。
可以看到,您使用了hash_fun函数对象类的)运算符重载方法,为Person类对象定制了合适的散列函数。 每次接收Person类的对象时,散列函数都会返回该对象的age成员变量的值。
实际上,默认散列函数的基础也是作为函数对象类实现的。
这样,在创建包含Person类对象的unordered_set容器时,可以将hash_fun作为参数传递给该容器模板类中的Pred参数。
STD : unordered _ set myset;
但是,此时创建的myset容器还不可用。 容器使用默认的std:equal_to比较规则,但该规则不适用于容器。
C无序集装箱自定义比较规则
与散列函数一样,创建任何无序容器时,都必须指定可以比较容器中每个元素是否相等的规则。
有趣的是,缺省情况下用于无序容器的std:equal_to比较规则本质上还是函数对象类,其底层实现如下:
template
class equal_to
{
公共:
bool operator () (const{ T _Left,const T _Right ) const ) )。
return(_left==_right );
}
(;
可以看到,在基本实现过程中,该规则直接用==运算符比较容器中的任意两个元素是否相等。 这意味着,如果容器支持直接使用==运算符比较存储在容器中的元素类型是否相等,则容器可以使用默认的std:equal_to比较规则。 相反,不能使用。
很明显,上面创建的myset容器内部包含Person类对象,不支持直接使用==运算符进行比较。 在这种情况下,可以通过以下两种方式解决此问题:
>在 Person 类中重载 == 运算符,这会使得 std::equal_to 比较规则中使用的 == 运算符变得合法,myset 容器就可以继续使用 std::equal_to 比较规则;以函数对象类的方式,自定义一个适用于 myset 容器的比较规则。
1) 重载==运算符
如果选用第一种解决方式,需要注意的是,C++ 中只能以成员函数的形式重载 == 运算符。仍以 Python 类为例,在此类的外部添加如下语句:
bool operator==(const Person &A, const Person &B) {
return (A.getAge() == B.getAge());
}
注意,这里在重载 == 运算符时,2 个参数必须用 const 修饰。
可以看到,通过此方式重载的运算符,当 std::equal_to 函数对象类中直接比较 2 个 Person 类对象时,实际上是在比较这 2 个对象的 age 成员变量是否相等。换句话说,此时的 std::equal_to 规则的含义为:只要 2 个 Person对象的 age 成员变量相等,就认为这 2 个 Person 对象是相等的。
重载 == 运算符之后,就能以如下方式创建 myset 容器:
std::unordered_set myset{ {"zhangsan", 40},{"zhangsan", 40},{"lisi", 40},{"lisi", 30} };
注意,虽然这里给 myset 容器初始化了 4 个 Person 对象,但由于比较规则以各个类对象的 age 值为准,myset 容器会认为前 3 个 Person 对象是相等的,因此最终 myset 容器只会存储 {"zhangsan", 40} 和 {"lisi", 30}。
2) 以函数对象类的方式自定义比较规则
除此之外,还可以完全舍弃 std::equal_to,以函数对象类的方式自定义一个比较规则。比如:
class mycmp {
public:
bool operator()(const Person &A, const Person &B) const {
return (A.getName() == B.getName()) && (A.getAge() == B.getAge());
}
};
在 mycmp 规则的基础上,我们可以像如下这样创建 myset 容器:
std::unordered_set myset{ {"zhangsan", 40},{"zhangsan", 40},{"lisi", 40},{"lisi", 30} };
由此创建的 myset 容器,虽然初始化了 4 个 Person 对象,但 myset 容器根据 mycmp 比较规则,可以识别出前 2 个是相等的,因此最终该容器内部存有 {"zhangsan", 40}、{"lisi", 40} 和 {"lisi", 30} 这 3 个 Person 对象。
总结
总的来说,当无序容器中存储的是基本类型(int、double、float、string)数据时,自定义哈希函数和比较规则,都只能以函数对象类的方式实现。
而当无序容器中存储的是用结构体或类自定义类型的数据时,自定义哈希函数的方式仍只有一种,即使用函数对象类的形式;而自定义比较规则的方式有两种,要么也以函数对象类的方式,要么仍使用默认的 std::equal_to 规则,但前提是必须重载 == 运算符。
如下是本节的完整代码,读者可直接拷贝下来,加深对本节知识的理解:
#include
#include
#include
using namespace std;
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;
}
//自定义哈希函数
class hash_fun {
public:
int operator()(const Person &A) const {
return A.getAge();
}
};
//重载 == 运算符,myset 可以继续使用默认的 equal_to 规则
bool operator==(const Person &A, const Person &B) {
return (A.getAge() == B.getAge());
}
//完全自定义比较规则,弃用 equal_to
class mycmp {
public:
bool operator()(const Person &A, const Person &B) const {
return (A.getName() == B.getName()) && (A.getAge() == B.getAge());
}
};
int main()
{
//使用自定义的 hash_fun 哈希函数,比较规则仍选择默认的 equal_to,前提是必须重载 == 运算符
std::unordered_set myset1{ {"zhangsan", 40},{"zhangsan", 40},{"lisi", 40},{"lisi", 30} };
//使用自定义的 hash_fun 哈希函数,以及自定义的 mycmp 比较规则
std::unordered_set myset2{ {"zhangsan", 40},{"zhangsan", 40},{"lisi", 40},{"lisi", 30} };
cout << "myset1:" << endl;
for (auto iter = myset1.begin(); iter != myset1.end(); ++iter) {
cout << iter->getName() << " " << iter->getAge() << endl;
}
cout << "myset2:" << endl;
for (auto iter = myset2.begin(); iter != myset2.end(); ++iter) {
cout << iter->getName() << " " << iter->getAge() << endl;
}
return 0;
}
程序执行结果为:
myset1:
zhangsan 40
lisi 30
myset2:
lisi 40
zhangsan 40
lisi 30