• rust重载比较运算符


    要重载比较运算符,需要为类型实现对应的trait。
    重载==和!=,需要实现PartialEq或者Eq
    重载<、<=、> 、 >=,需要实现PartialOrd或者Ord

    一、Eq/PartialEq

    为什么有两个trait呢?
    因为相等关系有两种:一种是完全相等关系,一种是部分相等关系。
    完全相等关系满足如下三个性质:
    自反性:自己一定等于自己,即a=a
    对称性:若有a=b,则有b=a
    传递性:若有a=bb=c,则有a=c

    部分相等关系只满足两个性质:
    对称性:若有a=b,则有b=a
    传递性:若有a=bb=c,则有a=c

    在浮点数类型中有个特殊的值是NaN(Not-a-number),这个值与任何值都不等,包括自己NaN != NaN,它就违背了自反性。所以判断浮点数是否相等就只能使用部分相等关系。

    部分相等是全相等关系的子集,也就是说,如果两个元素具有全相等关系,那它们之间也一定有部分相等关系。

    (一)PartialEq

    是部分相等关系。
    这个Trait中定义了两个方法:

    pub Trait PartialEq {
         fn eq(&self, other: &Rhs) -> bool;
         fn ne(&self, other: &Rhs) -> bool {
             !self.eq(other)
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    eq:两个值相等的话就返回true,需要使用者自行重载
    ne:两个值不相等的话就返回true,默认已经实现了

    所有的基本类型都实现了PartialEq

    1.为自定义类型实现PartialEq
    可使用#[derive(PartialEq)]由编译器自动实现

     #[derive(PartialEq)] 
     pub struct Person {
         pub id: u32,
         pub name: String,
         pub height: f64,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以自己手动实现

    impl PartialEq for Person {
         fn eq(&self, other: &Self) -> bool {
             self.id == other.id
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现时只需要实现eq方法即可,ne我们使用默认的。

    实现了PartialEq,就自动重载了==和!=运算符,下面就可以判断是否相等了

    fn main() {
         let p1 = Person {
             id: 0,
             name: "John".to_string(),
             height: 1.2,
         };
         let p2 = Person {
             id: 0,
             name: "Jack".to_string(),
             height: 1.4,
         };
         println!("p1 == p2 = {}", p1 == p2); // p1 == p2 = true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    例子

    fn main() {
         let f1 = f32::NAN;
         let f2 = f32::NAN;
         if f1 == f2 {
             println!("NaN竟然可以比较,这很不数学啊!")
         } else {
             println!("果然,虽然两个都是NaN,但是它们其实并不相等")
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.比较不同的类型
    给Rhs传入不同的类型,就能比较不同类型的相等性。
    示例代码如下:

    #[derive(PartialEq)]
    enum WheelBrand { 
    	Bmw, 
    	Benz, 
    	Michelin, 
    }
    struct Car { 
    	brand: WheelBrand, 
    	price: i32, 
    }
    impl PartialEq<WheelBrand> for Car {
         fn eq(&self, other: &WheelBrand) -> bool { 
    		self.brand == *other 
         }
    }
    fn main() {
         let car = Car { brand: WheelBrand::Benz, price: 10000 };
         let wheel = WheelBrand::Benz; // 比较struct和enum
         assert!(car == wheel);
         // assert!(wheel == car); // 无法反过来比较
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    代码仅实现了Car与Wheel的相等性比较,若要反过来比较,还得提供反向的实现,如下:

    impl PartialEq<Car> for WheelBrand {
         fn eq(&self, other: &Car) -> bool {
         	 *self == other.brand 
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (二)Eq

    就是完全相等关系

    这个trait继承了PartialEq,但没有添加新的方法,这个Trait只是告诉编译器,这是个完全相等关系而非部分相等关系。

    pub Trait Eq:PartialEq{}
    
    • 1

    在标准库中,只有f32和f64没有实现Eq

    1.为自定义类型实现Eq
    实现Eq不需要额外的代码,只需要实现PartialEq,并添加#[derive(Eq)]就可以了。

    实现了Eq的类型自然也重载了 == 和!=运算符,下面就可以判断是否相等了
    Rust中HashMap的key要求实现Eq,也就是要能完全相等,而浮点数由于没有实现Eq,因此不能用于HashMap的key

    二、Ord / PartialOrd

    大小关系也有两种:一种是全序关系,一种是偏序关系。
    全序关系有以下性质:

    完整的不对称性total antisymmetry:a < b,a == b,a > b这三种结果只有一个是真;
    可传递性transitive:如果a < b且b < c那么a < c;
    
    • 1
    • 2

    偏序关系有以下性质:

    不对称性antisymmetry:如果a < b那么 !(a > b);
    可传递性transitive:如果a < b且b < c那么a < c;
    
    • 1
    • 2

    还是因为特殊值NaN,NaN < 0 == false并且NaN > 0 == false并且(NaN == 0) == false

    (一)PartialOrd

    偏序关系

    PartialOrd继承了PartialEq,并且新定义了几个方法

    pub trait PartialOrd: PartialEq {
         fn partial_cmp(&self, other: &Rhs) -> Option;
         fn lt(&self, other: &Rhs) -> bool {
             matches!(self.partial_cmp(other), Some(Less))
         }
         fn le(&self, other: &Rhs) -> bool {
             matches!(self.partial_cmp(other), Some(Less | Equal))
         }
         fn gt(&self, other: &Rhs) -> bool {
             matches!(self.partial_cmp(other), Some(Greater))
         }
         fn ge(&self, other: &Rhs) -> bool {
             matches!(self.partial_cmp(other), Some(Greater | Equal))
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    partial_cmp:需要使用者重载,返回两值的比较结果;
    lt,le,gt,ge:默认定义好了;

    标准库里的所有基本类型都已实现该Trait

    1.为自定义类型实现PartialOrd
    要实现PartialOrd,必须同时实现PartialEq

    可使用#[derive(PartialOrd)]的方法实现该Trait,也可手动实现

    impl PartialOrd for Person {
         fn partial_cmp(&self,other:&Self) -> Option {
             self.height.partial_cmp(&other.height)
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现PartialOrd只需要实现partial_cmp方法即可,lt(),le(),gt() , ge() 使用默认的

    比较结果为Ordering枚举类型:

    pub enum Ordering {
         Less = -1,
         Equal = 0,
         Greater = 1,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为什么partial_cmp这个方法返回值类型是个Option, 而非直接是一个Ordering值类型?
    这仍然与浮点数类型有关, 因为NaN不是一个可以表示的数值, 诸如:3.0 < NaN这样的表达式毫无意义!对于这种情况,partial_cmp就会返回None。
    partial_cmp返回一个Option导致一个结果,当结果为None时, 无法决定两个值的排序,即x和y会处于不确定排序。只实现PartialOrd还不足以使你的自定义类型可排序,你还需要实现Ord。

    实现了PartialOrd的类型,会自动重载 <、<=、> 、 >= 运算符,下面就可以比较了
    例子

    fn main() {
         let p1 = Person {
             id: 0,
             name: "John".to_string(),
             height: 1.2,
         };
         let p2 = Person {
             id: 0,
             name: "Jack".to_string(),
             height: 1.4,
         };
         println!("p1 < p2 = {}", p1 < p2);
         println!("p1 <= p2 = {}", p1 <= p2);
         println!("p1 > p2 = {}", p1 > p2);
         println!("p1 >= p2 = {}", p1 >= p2);
         let x: f64 = std::f64::NAN;
         let y = 1.0f64;
         assert_eq!(x.partial_cmp(&y), None);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.比较不同类型

    (二)Ord

    全序关系
    Ord继承了PartialOrd和Eq,并且新定义了几个方法:

    pub trait Ord: Eq + PartialOrd {
         fn cmp(&self, other: &Self) -> Ordering;
         fn max(self, other: Self) -> Self{...}
         fn min(self, other: Self) -> Self{...}
         fn clamp(self, min: Self, max: Self) -> Self{...}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    cmp:需要使用者重载本方法,返回两值的比较结果;
    max,min,clamp:已经定义好了;

    在标准库中,只有f32和f64没有实现Ord

    1.为自定义类型实现Ord
    要实现Ord,必须要同时实现PartialOrd和Eq。实现PartialEq,PartialOrd以及Ord时要特别注意彼此之间不能冲突
    例子
    vector中的sort方法要求类型实现了Ord

    use std::cmp::Ordering;
    #[derive(Debug,Eq)]
    pub struct Person {
         pub id: u32,
         pub name: String,
         pub height: f64,
    }
    impl PartialEq for Person {
         fn eq(&self, other: &Self) -> bool { self.id == other.id }
    }
    impl PartialOrd for Person {
         fn partial_cmp(&self, other: &Self) -> Option {
             self.id.partial_cmp(&other.id)
         }
    }
    impl Ord for Person {
         fn cmp(&self, other: &Self) -> Ordering {
             self.id.cmp(&other.id)
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    实现Ord只需要实现cmp方法即可,max() 和min()使用默认的。

    例子

    enum BookFormat3 {
        Paperback,
        Hardback,
        Ebook,
    }
    
    struct Book3 {
        name: String,
        format: BookFormat3,
    }
    
    // -- 先实现 PartialEq
    impl PartialEq for Book3 {
        fn eq(&self, other: &Book3) -> bool {
            self.name == other.name
            // 这里假设format字段不要求比较
        }
    }
    
    // -- 再实现 Eq
    impl Eq for Book3 {}
    
    // -- 再实现 Ord
    impl Ord for Book3 {
        fn cmp(&self, other: &Book3) -> Ordering {
            // 直接调用name(String)的cmp方法(当需要实现Ord时,成员字段一般都实现了Ord,可直接调用其cmp方法)
            self.name.cmp(&other.name)
        }
    }
    
    // -- 最后实现 PartialOrd
    impl PartialOrd for Book3 {
        fn partial_cmp(&self, other: &Book3) -> Option {
            // 直接调用上面实现的cmp方法
            Some(self.cmp(&other))
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    实现Ord的时候需要同时实现PartialOrd,PartialOrd的partial_cmp()内部调用的是Ord的cmp(),理由是既然一开始就想要为类型实现Ord,说明类型是能够得出一个肯定结果的(非None)

    实现了Ord之后,会自动重载 <、<=、> 、 >= 运算符,下面就可以比较了
    例子

    use std::cmp::Ordering;
    let x = 1;
    let y = 2;
    assert_eq!(x.cmp(&y), Ordering::Less);
    assert_eq!(y.cmp(&x), Ordering::Greater);
    assert_eq!(x.cmp(&x), Ordering::Equal);
    assert_eq!((-3).clamp(-2, 1), -2);
    assert_eq!(0.clamp(-2, 1), 0);
    assert_eq!(2.clamp(-2, 1), 1);
    assert_eq!(1.min(2), 1);
    assert_eq!(2.min(2), 2);
    assert_eq!(1.max(2), 2);
    assert_eq!(2.max(2), 2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    例子

    fn main() {
         let mut v = vec![
             Person {
                 id: 3,
                 name: "".to_string(),
                 height: 3.0,
             },
             Person {
                 id: 2,
                 name: "".to_string(),
                 height: 4.0,
             },
             Person {
                 id: 1,
                 name: "".to_string(),
                 height: 5.0,
             },
         ];
         v.sort();
         println!("{:?}", v); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.比较不同类型

    三、关于效率

    你也许好奇,到底是自己手动实现的效率高, 还是auto-derive效率高?这很难说,不能一概而论,比如:如果你明确定知道一个大struct中只有少数几个成员与此比较直接相关,或者说有决定性, 那么你自己手动实现的版本很可能优于auto-derive版本, 因为auto-derive版本通常会依次比较所有成员,很可能做了无用功。换一个角度说, 尽管auto-derive版本可能会依次检查比较每一个struct成员, 但是因为它可以采用布尔表达式的短路原则, 也许检查第一个成员就停止了,因此也很有可能快于自定义实现版。但是Hash是一个例外,它不允许短路原则,必须所有成员依次都要哈希一次才可以,不能偷懒,但如果你能够只哈希1或2个简单成员而不是大量字符串成员,那么您将很容易击败auto-derive默认实现。

  • 相关阅读:
    联盟营销最佳实践:提高联盟计划的投资回报率
    利用Spring Boot后端与Vue前端技术构建现代化电商平台
    建造者模式
    如何让设计师快速提高设计美感?这5个网站就够了
    入门力扣自学笔记84 C++ (题目编号736)(未理解)
    HTTP协议(超级详细)
    C++ - unordered系列关联式容器介绍 - 和 set map 的比较
    【SQL】之索引
    容器化 Spring Boot 代码的 9 个技巧
    玩转Mysql系列 - 第20篇:异常捕获及处理详解
  • 原文地址:https://blog.csdn.net/inxunxun/article/details/134039577