C++中set/unordered_set 自定义比较规则


Heroes 类为例演示
如何自定义set/unordered_set内外部比较器 ,类
Heroes定义如下:

class Heroes {
public:   
    Heroes(string _name, int _age) :name(_name), age(_age) {}
private:
    string name;//私有变量 name
    int age;//私有变量 age
};

set

现在我们需要使用 set 容器对Heroes类的一些对象进行存储,我们知道set的底层实现是RB-Tree(红黑树),因此如何对这些对象进行排序就成了我们必须手动完成的事情了。
下面是set容器用到的参数列表:

template <class _Kty, class _Pr = less<_Kty>, class _Alloc = allocator<_Kty>>

其中提供了默认的排序规则less:

// STRUCT TEMPLATE less
template <class _Ty = void>
struct less {
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;

    constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
        return _Left < _Right;
    }
};

less中对 () 进行了重载,因此我们也可以仿照它定义比较器。

set外部比较器

class externalCmp{
public:
    bool operator()(const Heroes& a, const Heroes& b) const{
        return a.age > b.age;//set内部按照自定义规则排序
    }
};

该比较是将Heroes对象按照年龄从大到小存储。
外部比较器使用示例:

//使用 外部比较器 按照既定规则排序 
void set_方法1() {  
    set<Heroes, externalCmp> hero;
    hero.insert({ "刘备",30 });
    hero.insert({ "关羽", 29 });
    hero.insert({ "张飞", 34 });
    hero.insert({ "黄忠", 62 });
    hero.insert({ "马超", 25 });
    hero.insert({ "赵云", 27 });
    for (auto& it : hero) {
        cout << it << endl;
    }
}

注意:由于Heroes类中的成员属性都设置为了私有,这里用到自己定义的类需要在Heroes类中形成为友元关系。(或者将属性改为public)
调用set_方法1()
特别的,细心的小伙伴是不是发现输出的格式有点意外,没错,这里对 << 进行了重载:

//重载 << 操作符,让其直接输出 Heroes 对象信息
ostream& operator<<(ostream& O,const Heroes& os) {
    O <<"英雄:" <<os.name <<" 年龄:"<< os.age<<"岁";
    return O;
}

set内部比较器

在自定义类中对 < 进行重载即可。

 //内部重载 < 自定义排序规则
    constexpr bool operator<(const Heroes& hero)const {
        return age<hero.age;
    }

效果与上面的外部比较器一致,就不多加赘述了。

unordered_set

与set不同的是,unordered_set底层是以哈希表为基础的一种容器,让我们看看头文件中对它的定义:

template <class _Kty, class _Hasher = hash<_Kty>, class _Keyeq = equal_to<_Kty>, class _Alloc = allocator<_Kty>>

以上为unordered_set()的参数列表,其中后面三个均为可缺省参数,默认为我们提供了 hash<_Kty>、equal_to<_Kty>(_Kty为类模板)
hash<类型> 的作用是根据传递的参数通过哈希函数生成下标
在头文件中定义分别如下:

// STRUCT TEMPLATE hash
template <class _Kty>
struct hash
    : _Conditionally_enabled_hash<_Kty,
          !is_const_v<_Kty> && !is_volatile_v<_Kty> && (is_enum_v<_Kty> || is_integral_v<_Kty> || is_pointer_v<_Kty>)> {
    // hash functor primary template (handles enums, integrals, and pointers)
    static size_t _Do_hash(const _Kty& _Keyval) noexcept {
        return _Hash_representation(_Keyval);
    }
};

unordered_set外部比较器

我们可以仿照其在外部定义一个类实现hash功能:

class hash_Heroes {
public:
    size_t operator()(const Heroes& hero)const {
        hash<string> hs;
        return hs(hero.name);//哈希值由对象的name属性决定
    }
};

**equal_to<_Kty>**则定义的是如何判断两个对象是否相同。
同样我们找出其在库文件中的定义:

// STRUCT TEMPLATE equal_to
template <class _Ty = void>
struct equal_to {
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;

    constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
        return _Left == _Right;
    }
};

不难发现函数中仅是对 **()**进行了重载,因此我们仿照其定义自己的判定规则:

class equal_to_Heroes {
public:
    bool operator()(const Heroes& hero1, const Heroes& hero2)const {
        return hero1.name == hero2.name && hero1.age== hero2.age;
    }//只有当姓名与年龄均相同时破判定为相同对象
};

外部比较器使用示例:

//传入指定的模板参数,告诉容器如何哈希一个Heroes对象,以及如何比较两个Heroes对象是否相同
void unordered_set方法1() {
    unordered_set<Heroes, hash_Heroes, equal_to_Heroes> hero3;
    hero3.insert({ "刘备",30 });
    hero3.insert({ "关羽", 29 });
    hero3.insert({ "张飞", 34 });
    hero3.insert({ "黄忠", 62 });
    hero3.insert({ "马超", 25 });
    hero3.insert({ "赵云", 27 });
    hero3.insert({ "赵云", 28 });
    hero3.insert({ "黄忠", 70 });
    for (auto& it : hero3) {
        cout << it << endl;
    }
}

调用unordered_set方法1()

unordered_set内部比较器

这里仅对 equal_to<> 内部进行修改:重载比较运算符 == 即可:

 //内部重载 == 自定义哈希集合如何判断对象为相同
    const bool operator==(const Heroes& hero)const {
        return name == hero.name && age == hero.age;
    }

代码

#include <iostream>
#include <set>
#include <unordered_set>
#include <string>
using namespace std;

class Heroes {
public:   
    Heroes(string _name, int _age) :name(_name), age(_age) {}
    
    friend class externalCmp;
    friend class intervalCmp;

    friend class hash_Heroes;
    friend class equal_to_Heroes;

    //内部重载 < 自定义排序规则
    constexpr bool operator<(const Heroes& hero)const {
        return age > hero.age;
    }
    //内部重载 == 自定义哈希集合如何判断对象为相同
    const bool operator==(const Heroes& hero)const {
        return name == hero.name && age == hero.age;
    }
    //重载输出符,便于统一输出
    friend ostream& operator<<(ostream& O, const Heroes& os);
private:
    string name;
    int age;
};

class externalCmp{
public:
    bool operator()(const Heroes& a, const Heroes& b) const{
        return a.age > b.age;//set内部按照自定义规则排序
    }
};

//使用 外部比较器 按照既定规则排序 
void set_方法1() {  
    set<Heroes, externalCmp> hero;
    hero.insert({ "刘备",30 });
    hero.insert({ "关羽", 29 });
    hero.insert({ "张飞", 34 });
    hero.insert({ "黄忠", 62 });
    hero.insert({ "马超", 25 });
    hero.insert({ "赵云", 27 });
    for (auto& it : hero) {
        cout << it << endl;
    }
}
//改变 内部比较器 operator< 定义其排序规则
void set_方法2() {
    set<Heroes> hero2;
    hero2.insert({ "刘备",30 });
    hero2.insert({ "关羽", 29 });
    hero2.insert({ "张飞", 34 });
    hero2.insert({ "黄忠", 62 });
    hero2.insert({ "马超", 25 });
    hero2.insert({ "赵云", 27 });
    for (auto& it : hero2) {
        cout << it << endl;
    }
}
class hash_Heroes {
public:
    size_t operator()(const Heroes& hero)const {
        hash<string> hs;
        return hs(hero.name);
    }
};

class equal_to_Heroes {
public:
    bool operator()(const Heroes& hero1, const Heroes& hero2)const {
        return hero1.name == hero2.name && hero1.age==hero2.age;
    }
};
//传入指定的模板参数,告诉容器如何哈希一个Heroes对象,以及如何比较两个Heroes对象是否相同
void unordered_set方法1() {
    unordered_set<Heroes, hash_Heroes, equal_to_Heroes> hero3;
    hero3.insert({ "刘备",30 });
    hero3.insert({ "关羽", 29 });
    hero3.insert({ "张飞", 34 });
    hero3.insert({ "黄忠", 62 });
    hero3.insert({ "马超", 25 });
    hero3.insert({ "赵云", 27 });
    hero3.insert({ "赵云", 28 });
    hero3.insert({ "黄忠", 70 });
    for (auto& it : hero3) {
        cout << it << endl;
    }
}

void unordered_set方法2() {
    hash<Heroes>;//根据一个Heroes对象生成一个下标
    equal_to<Heroes>;//比较两个对象是否相同的标准
    unordered_set<Heroes, hash_Heroes> hero4;
    hero4.insert({ "刘备",30 });
    hero4.insert({ "关羽", 29 });
    hero4.insert({ "张飞", 34 });
    hero4.insert({ "黄忠", 62 });
    hero4.insert({ "马超", 25 });
    hero4.insert({ "赵云", 27 });
    hero4.insert({ "赵云", 28 });
    for (auto& it : hero4) {
        cout << it << endl;
    }
}

//重载 << 操作符,让其直接输出 Heroes 对象信息
ostream& operator<<(ostream& O, const Heroes& os) {
    O << "英雄:" << os.name << " 年龄:" << os.age << "岁";
    return O;
}

int main() {
    set_方法1();
	return 0;
}

参考资料

C++ STL unordered_set容器完全攻略
C++ set与map、unordered_map、unordered_set与哈希表

原文链接: https://www.cnblogs.com/bincodes/p/15191719.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C++中set/unordered_set 自定义比较规则

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/208366

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 下午11:22
下一篇 2023年2月12日 下午11:22

相关推荐