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与哈希表

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
unordered_set和unordered_map是C++11新增加的两个关联式容器,它们的区别主要体现在以下几个方面: 1. 底层实现:unordered_set和unordered_map的底层都是哈希表,而set和map的底层是红黑树。哈希表是一种根据键值直接进行访问的数据结构,而红黑树是一种自平衡的二叉搜索树。 2. 排序:unordered_set是不可排序的,而set是有序的。unordered_map是无序的,而map是有序的。这是因为哈希表是根据键值的哈希值进行存储和访问的,没有固定的顺序。 3. 迭代器:unordered_set和unordered_map使用的是单向迭代器,而set和map使用的是双向迭代器。单向迭代器只能从前往后遍历容器的元素,而双向迭代器可以从前往后和从后往前遍历。 4. 效率:由于底层实现的不同,unordered_set和unordered_map的插入、查找和删除操作的时间复杂度都是O(1),而set和map的时间复杂度是O(logN)。因此,unordered_set和unordered_map相对于set和map来说,在大部分情况下具有更高的效率。 综上所述,unordered_set和unordered_map与set和map在底层实现、排序、迭代器和效率上存在一些区别。选择使用哪个容器取决于具体的需求和性能要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C++ 哈希表及unordered_set + unordered_map容器](https://blog.csdn.net/qq_60750110/article/details/126746419)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [算法(42)-数组等值切割-前缀累加和-哈希表Map-set版-C++](https://download.csdn.net/download/weixin_38710566/14039060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nepu_bin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值