对于一个没接触过.net的Java程序员来说,在.net里最不适应的大概就是字符串之间的比较居然可以用“==”运算符。因为在Java里用“==”运算符去比较两个对象是否相等,是根据两个对象所在的内存地址是否相同来比较的。
那为什么.net就可以用“==”运算符来比较两个的字符串内容是否相等呢?原因就如标题所说,.net支持运算符的重载,也正是微软在写String类的时候已经帮我们重写了“==”运算符,我们才能通过简单的“==”运算符去比较两个字符串内容是否相同,而不需要去调用字符串的Equals()方法。
我们可以将光标放在string类上,按F12进入string类内部。我们可以看到如下两行代码:
1 2 |
public static bool operator !=(string a, string b); public static bool operator ==(string a, string b); |
这就是我之前说的“微软帮我们重写了‘==’运算符”的证据。
1.重载运算符关键字
重载运算符通过“operator”关键字声明。
一般来说大概都是以下的格式(下面是二元运算符重写的格式):
1 2 3 4 |
public static 返回类型 operator 运算符(T t1, T t2) { …… } |
一般来说运算符重载的返回类型没有强制要求。就算是比较运算符只要您乐意也可以用除bool以外的类型作为返回值。
2.可以重载的运算符
运算符 | 可重载性 |
+、-、!、~、++、--、true、false | 这些一元运算符可以进行重载。 |
+, -, *, /, %, &,|, ^, <<, >> | 可以重载这些二元运算符。 |
==, !=, <, >, <=, >= | 比较运算符可以进行重载(但是请参阅此表后面的备注)。 |
&&, || | 条件逻辑运算符无法进行重载,但是它们使用 & 和 |(可以进行重载)来计算。 |
[] | 数组索引运算符无法进行重载,但是可以定义索引器。 |
(T)x | 强制转换运算符无法进行重载,但是可以定义新转换运算符(请参阅 explicit 和 implicit)。 |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= | 赋值运算符无法进行重载,但是 +=(举例)使用 +(可以进行重载)来计算。 |
=、.、?:、??、->、=>、f(x)、as、checked、unchecked、default、delegate、is、new、sizeof、typeof | 这些运算符无法进行重载。 |
备注:如果需要对比较运算符进行重载,则必须同时对相对的比较运算符进行重载。例如您需要重载“==”运算符,那您也必须重载“!=”运算符。
摘自微软MSDN:可重载运算符(C# 编程指南)
3.实际使用
假设我们写了一个Person类,其内部如下:
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 |
public class Person { /// <summary> /// 身份证号 /// </summary> public int ID { set; get; } /// <summary> /// 名字 /// </summary> public string Name { set; get; } /// <summary> /// 年龄 /// </summary> public int Age { set; get; } /// <summary> /// 性别 /// </summary> public char Gender { set; get; } /// <summary> /// 构造函数 /// </summary> /// <param name="id">身份证号</param> /// <param name="name">名字</param> /// <param name="age">年龄</param> /// <param name="gender">性别</param> public Person(int id,string name,int age,char gender) { this.ID = id; this.Name = name; this.Age = age; this.Gender = gender; } } |
(1)重载“==”运算符和“!=”运算符。
我们认为比较两个人是否相同是根据两者身份证号是否相同来判断的。
在Person类中我们写入以下代码:
1 2 3 4 5 6 7 |
public static bool operator ==(Person p1, Person p2) { if (p1.ID == p2.ID) return true; else return false; } |
不要忘记比较运算符的规则,如果您就重写了“==”运算符,编译是会报错的:
补上“!=”运算符重载。需要注意的是这边重载的运算符是“!=”,所以当两者不相等的时候才返回True。
1 2 3 4 5 6 7 |
public static bool operator !=(Person p1, Person p2) { if (p1.ID != p2.ID) return true; else return false; } |
现在编译是会通过了,但是会警告我们重写了“==”和“!=”运算符但没有重写“Equals(object o)”和“GetHashCode()”方法。
真麻烦呐,我们还是都补齐好了:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public static bool operator ==(Person p1, Person p2) { bool result; if (((object)p1 == null) || ((object)p2 == null)) { if (((object)p1 == null) && ((object)p2 == null)) result = true; else result = false; } else { result = p1.Equals(p2); } return result; } public static bool operator !=(Person p1, Person p2) { return !(p1 == p2); } public override int GetHashCode() { return base.GetHashCode(); } public override bool Equals(object obj) { if (obj == null) { return false; } else if (obj == (object)this) { return true; } else if (!(obj is Person)) { return false; } else { Person person = (Person)obj; if (this.ID == person.ID) return true; else return false; } } |
注意:您已经重写了Person类的“==”运算符,所以您不能直接在public static bool operator ==(Person p1, Person p2)方法里直接使用“==”运算符去判断参数是否为null,这样会造成无限递归致使运行时抛出“StackOverflowException”异常。所以需要将Person类型强制转换为object类型(object类是所有类的基类),再去判断是否为null。
现在我们可以测试一下,先是给两个Person赋不一样的ID值:
然后是赋上相同的ID值:
(2)重载“-”运算符(一元运算符)。
“-”这个符号又可以表示一元运算符(取负),也可以表示二元运算符“相减”。
我们先讨论一元运算符的情况,我们假设Person取负是将年龄取负并返回。
代码如下:
1 2 3 4 |
public static int operator -(Person p) { return -p.Age; } |
运行结果:
(3)重载“-”运算符(二元运算符)。
我们假设两个Person类相减返回两个Person的年龄差。
代码如下:
1 2 3 4 |
public static int operator -(Person p1, Person p2) { return p1.Age - p2.Age; } |
运行结果:
写在最后:
如果我们重写了“==”运算符,但又需要判断这两个对象是不是引用同一个堆栈地址该怎么办呢?其实我之前已经略有提及:我们可以将两个待比较的对象强制类型转换为object类型。object类型的“==”运算符就是比较两者的堆栈地址的。
学会使用C#的可重载运算符,可以使我们的开发效率大大提升。让那帮Java程序员羡慕去吧。
小柊
2016年8月18日 00:04:35