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

本文详细介绍了C++中set与unordered_set的内部与外部比较器的实现,以Heroes类为例,展示了如何通过自定义比较器对外部排序规则和哈希规则进行设定,包括set的红黑树排序和unordered_set的哈希表存储。同时,文章还提供了重载输出操作符的示例,帮助理解容器中对象的输出方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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

### C++ Set 容器比较器的用法和实现 #### 自定义比较函数对象 为了自定义 `std::set` 的排序方式,可以创建一个仿函数(functor),该类重载了 `operator()` 方法。这允许更复杂的逻辑来决定两个元素之间的顺序。 ```cpp struct CustomComparator { bool operator()(const int& lhs, const int& rhs) const { return abs(lhs) < abs(rhs); } }; ``` 通过这种方式定义了一个基于绝对值大小来进行比较的操作符[^2]。 #### 使用 lambda 表达式作为比较器 除了传统的仿函数外,在现代 C++ 中还可以利用lambda表达式的简洁语法来构建临时匿名函数并将其传递给集合模板参数: ```cpp auto cmp = [](int x, int y){return std::abs(x)<std::abs(y); }; std::set<int, decltype(cmp)> s(cmp); // 或者直接在声明时指定 std::set<int, decltype([](int x, int y)->bool{return std::abs(x)<std::abs(y);})> anotherSet{[](int x, int y)->bool{return std::abs(x)<std::abs(y);}}; s.insert(-5); s.insert(3); for (auto elem : s){ std::cout << elem << " "; } ``` 这段代码展示了如何使用带有捕获列表为空的标准库算法中的谓词形式化描述规则。 #### 构造具有特定分配策略的安全容器实例 当涉及到内存管理和异常安全性时,可以通过引入适配器模式以及智能指针机制确保资源释放不会因为中途抛出而失败。下面的例子说明了这一点: ```cpp template<typename T> class SafeAllocator{ public: using value_type = T; template<typename U> struct rebind { typedef SafeAllocator<U> other; }; pointer allocate(size_t n, const void* hint=0){ try{ auto p = static_cast<pointer>(::operator new(n * sizeof(T))); assert(p != nullptr && "Allocation failed"); return p; }catch(...){ throw bad_alloc(); } } private: unique_ptr<T[],decltype(&free_allocator)> _data{nullptr,&free_allocator}; }; typedef set<int, less<int>,SafeAllocator<int>> IntSet; IntSet safe_set; safe_set.insert(10); if(!safe_set.empty()){ cout<<(*safe_set.begin())<<"\n"; } void free_allocator(void*p){ ::operator delete(p); } ``` 此部分实现了安全分配处理程序,并保证即使发生构造期间引发异常也能正确清理已分配的空间[^3]。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nepu_bin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值