zoukankan      html  css  js  c++  java
  • 对象的比较与排序(四):对象的相等判断Equals和IEquatable<T>(转)

    本节覆盖了以下知识点:
    一、Object.Equals()方法;
    二、“==”,“!=”运算符重载;
    三、IEquatable<T> 接口。
     
    一、重写Equals方法。
    众所周知,Object类型有一个名为Equals的实例方法可以用来确定两个对象是否相等。Object的Equals的默认实现比较的是两个对象的引用等同性。而Object的派生类ValueTpye重写了Equals方法,它比较的是两个对象的逻辑等同性。也就是说,在C#里,引用类型的默认Equals版本关注的是引用等同性,而值类型关注的是逻辑等同性。当然,这并不总能满足我们的要求。所以每当我们更在意引用类型的逻辑等同性的时候,我们就应该重写Equals方法。
    上一个日记通过实现IComparable<T>接口的对象可以相互比较,但比较结果是0,1和-1,显得不直观,在许多情况下,可能只需要知道两个对象是否相等,而不需要知道这两个对象
    “谁大谁小”,这时,可以重写object类的 Equals方法。
        public override bool Equal(object obj)
        {
            if(this.CompareTo(obj)==0)
                return true;
            else
                return false;
        }
        
    可以看到,Equals方法的内部调用CompareTo方法进行对象比较。
     
    总结:
    C#中有两种不同的相等:引用相等和值相等。值相等,即两个对象包含相同的值。例如,两个值为2的整数数具有值相等性。引用相等意味着要比较的两个对象不是两个对象,而是两个“对象引用”,这两个“对象引用”引用的是同一个对象。
    1、对于值类型
      对于值类型,如果对象的值相等,则相等运算符 (==) 返回 true,否则返回 false。
    2、对于引用类型
      对于string 以外的引用类型,如果两个对象引用同一个对象,则 == 返回 true。对于 string 类型,== 比较字符串的值。 
      ==操作比较的是两个变量的值是否相等。 
      equals()方法比较的是两个对象的内容是否一致.equals也就是比较引用类型是否是对同一个对象的引用。
      首先我们看一段程序:
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        namespace CSharpDemo
        {
            class Person
            {
                private string name;
                public string Name
                {
                    get { return name; }
                    set { name = value; }
                }
                public Person(string name)
                {
                    this.name = name;
                }
            }
            class Program
            {
                static void Main(string[] args)
                {
                    int i = 1;
                    int j = 1;
                    
                    Console.WriteLine(i == j); //True
                    Console.WriteLine(i.Equals(j)); //True
                    string a = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
                    string b = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
                    Console.WriteLine(a == b); //True
                    Console.WriteLine(a.Equals(b)); //True
                    object g = a;
                    object h = b;
                    Console.WriteLine(g == h); //False
                    Console.WriteLine(g.Equals(h)); //True
                    Person p1 = new Person("person");
                    Person p2 = new Person("person");
                    Console.WriteLine(p1 == p2); //False
                    Console.WriteLine(p1.Equals(p2)); //False
                    Person p3 = new Person("person");
                    Person p4 = p3;
                    Console.WriteLine(p3 == p4); //True
                    Console.WriteLine(p3.Equals(p4)); //True
                    Console.ReadKey();
                }
            }
        }
    Equals方法的调用输出情况
    输出结果在对应的行后面标出。
      为什么会出现这个答案呢?因为值类型是存储在内存中的堆栈(以后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中。 
    ==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。 
    equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同。 
    而字符串是一个特殊的引用型类型,在C#语言中,重载了string 对象的很多方法方法(包括equals()方法),使string对象用起来就像是值类型一样。 
    因此在上面的例子中,字符串a和字符串b的两个比较是相等的。 
    对于object g 和object h 时内存中两个不同的对象,所以在栈中的内容是不相同的,故不相等。而g.equals(h)用的是string的equals()方法故相等(多态)。如果将字符串a和b作这样的修改: 
    string a="aa"; 
    string b="aa"; 
    则,g和h的两个比较都是相等的。这是因为系统并没有给字符串b分配内存,只是将"aa"指向了b。所以a和b指向的是同一个字符串(字符串在这种赋值的情况下做了内存的优化)。 
      对于p1和p2,也是内存中两个不同的对象,所以在内存中的地址肯定不相同,故p1==p2会返回false,又因为p1和p2又是对不同对象的引用,所以p1.equals(p2)将返回false。 
      对于p3和p4,p4=p3,p3将对对象的引用赋给了p4,p3和p4是对同一个对象的引用,所以两个比较都返回true。
     
    二、运算符重载
    PS:若一个对象重载了"=="运算符,则也必须重载"!="运算符,这是一个基本原则。
    关于运算符重载,请参见下一个日记。
     
     总结ReferenceEquals,Equals, == 的区别:
      ReferenceEquals:静态方法,不能重写,只能比较引用,如果有一个参数为null会返回false,不会抛出异常,如果比较值类型,则始终返回false。
      Equals:实例方法,默认可以比较引用也可以比较值,可以重写。可以按值比较对象。
      静态Equals:静态方法,不能重写。如果没有重写Equals,比较引用,或者比较值。如果重载了Equals方法。比较引用,或者比较值,或者按重写的Equals比较,如果其中一个参数为null,抛出异常
      ==运算符:可以按引用比较,也可以按值比较。可以重写。是操作运算符。
      最后需要的是,如果重载了Equals,则最好是重载GetHashCode,必须重载==运算符。
     
    三、IEquatable<T> 接口
    在某些场合可能不需要判断对象的大小,而仅需要给一个“两个对象是否相等”的结论,为些.NET又定义了一个专用于“比较对象是否相等”的接口
        public interface IEquatable<T>
        {
            bool Equals(T other);
        }
        
    关于这个接口和前面几个日记提到的接口,具体可以参见:
    http://www.cnblogs.com/symphony2010/archive/2011/08/19/2145890.html
     
    四、综合示例
    总结前面的几个日记,现给出一个综合示例
     
    定义一个Circle类
        using System;
        using System.Collections.Generic;
        using System.Text;
        namespace ObjectCompare
        {
            /// <summary>
            /// 圆心
            /// </summary>
            public struct CircleCenter
            {
                public double x;
                public double y;
            }
            class Circle:IComparable,IComparable<Circle>,IEquatable<Circle>
            {
                public double Radius = 0;   //半径
                public CircleCenter center ; //圆心
                //实现IComparable接口定义的方法
                public int CompareTo(object obj)
                {
                    if (!(obj is Circle))
                        throw new ArgumentException("只能比对Cirlce对象");
                    return CompareTo(obj as Circle);
                   
                }
                //实现IComparable<T>接口定义的方法
                public int CompareTo(Circle other)
                {
                    double ret = Math.Abs(other.Radius - this.Radius);
                    if (ret < 1e-3)
                        return 0;
                    if (other.Radius < this.Radius)
                        return 1;
                    return -1;
                }
                //覆盖Object类的GetHashCode方法
                public override int GetHashCode()
                {
                    //整数部分与小数点后3位相同的对象生成相同的哈希值
                    return (int)(Radius*1000);
                }
                //重写Object类的Equals方法
                public override bool Equals(object obj)
                {
                    if (this.CompareTo(obj) == 0)
                        return true;
                    return false;
                }
                //实现IEquatable<Circle>接口定义的方法
                public bool Equals(Circle other)
                {
                    return this.CompareTo(other) == 0;
                }
                //----------------------------------------
                //重载相关的运算符
                //----------------------------------------
                public static bool operator ==(Circle obj1, Circle obj2)
                {
                    return obj1.Equals(obj2);
                }
                public static bool operator !=(Circle obj1, Circle obj2)
                {
                    return !(obj1.Equals(obj2));
                }
                public static bool operator >(Circle obj1, Circle obj2)
                {
                    if (obj1.CompareTo(obj2) > 0)
                        return true;
                    return false;
                }
                public static bool operator <(Circle obj1, Circle obj2)
                {
                    if (obj1.CompareTo(obj2) < 0)
                        return true;
                    return false;
                }
                public static bool operator <=(Circle obj1, Circle obj2)
                {
                    if ((obj1.CompareTo(obj2) < 0) || (obj1.CompareTo(obj2) == 0))
                        return true;
                    return false;
                }
                public static bool operator >=(Circle obj1, Circle obj2)
                {
                    if ((obj1.CompareTo(obj2) > 0) || (obj1.CompareTo(obj2) == 0))
                        return true;
                    return false;
                }
            }
        }
        
        测试代码
        using System;
        using System.Collections.Generic;
        using System.Text;
        namespace ObjectCompare
        {
            class Program
            {
                static void Main(string[] args)
                {
                    Circle obj1 = new Circle { Radius = 100.1 };
                    Circle obj2 = new Circle { Radius = 100.9 };
                    //测试覆盖的方法
                    Console.WriteLine(obj1.GetHashCode());//100100
                    Console.WriteLine(obj2.GetHashCode());//100900
                    Console.WriteLine(obj1.CompareTo(obj2)); //-1
                    Console.WriteLine(obj1.Equals(obj2));//false
                    //测试重载的运算符
                    Console.WriteLine(obj1 == obj2);    //false
                    Console.WriteLine(obj1 != obj2);    //true
                    Console.WriteLine(obj1 >= obj2);    //false
                    //以下测试Circle对象数组的排序功能
                    Circle[] circles = new Circle[10];  //创建Circle对象数组
                    Random ran = new Random();
                    for (int i = 0; i < 10; i++)
                    {
                        circles[i] = new Circle { Radius = ran.Next(1, 1000)/100.0 };
                    }
                    Console.WriteLine("原始数组:");
                    Array.ForEach<Circle>(circles, (circle) => { Console.WriteLine("圆对象的哈希代码:{0},半径:{1}", circle.GetHashCode(), circle.Radius); });
                    Console.WriteLine("\n排序之后:");
                    Array.Sort(circles);
                    Array.ForEach<Circle>(circles, (circle) => { Console.WriteLine("圆对象的哈希代码:{0},半径:{1}", circle.GetHashCode(), circle.Radius); });
                    Console.ReadKey();
                }
            }
        }
  • 相关阅读:
    multiprocessing 多进程实现 生产者与消费者模型JoinableQueue
    条件锁condition与Queue()
    threading 官方 线程对象和锁对象以及条件对象condition
    【NOIp训练】—子串查找VII(AC自动机+树链剖分+线段树)
    【NOIp训练】—子串查找VII(AC自动机+树链剖分+线段树)
    【HDU 5628】—Clarke and math(狄利克雷卷积快速幂)
    【HDU 5628】—Clarke and math(狄利克雷卷积快速幂)
    【NOIp2019模拟】题解
    【NOIp2019模拟】题解
    【NOIp2019模拟】题解
  • 原文地址:https://www.cnblogs.com/zhangpengshou/p/2359986.html
Copyright © 2011-2022 走看看