Fork me on GitHub

学习运算符重载

在许多情况下,重载运算符允许生成可读性更高、更直观.我认为即便是你学什么语言的编程你都在有意无意的接触过编译器预定义的运算符重载只是没有那个概念罢了,下面是我在学习过程中的总结。
运算符的工作方式 :为了理解运算符是如何重载的,我先了解一下编译器遇到运算符时会做怎么的处理,我们用相加运算符+作为例子来讲解。假定编译器遇到下面的代码:
 

int a = 1;
uint b = 2;
double c = 3.0;
long l = a + b;
double d = a+c;

long l = a + b;编译器知道它需要把两个整数加起来,并把结果赋予long。调用一个方法把数字加在一起时,表达式a+b是一种非常直观、方便的语法。该方法带有两个参数a和b,并返回它们的和。所以它完成的任务与任何方法调用是一样的, 它会根据参数类型查找最匹配的+运算符重载,这里是带两个整数参数的+运算符重载。与一般的重载方法一样,预定义的返回类型不会因为调用的方法版本而影响编译器的选择。在本例中调用的重载方法带两个int类型参数,返回一个int,这个返回值随后会转换为long。
下一行代码让编译器使用+运算符的另一个重载版本:double d=a + c;在这个例子中,参数是一个int类型的数据和一个double类型的数据,但+运算符没有带这种复合参数的重载形式,所以编译器认为,最匹配的+运算符重载是把两个double作为其参数的版本,并隐式地把int转换为double。把两个double加在一起与把两个整数加在一起完全不同,浮点数存储为一个尾数和一个指数。把它们加在一起要按位移动一个double的尾数,让两个指数有相同的值,然后把尾数加起来,移动所得尾数的位,调整其指数,保证答案有尽可能高的精度。
现在,看看如果编译器遇到下面的代码,会发生什么:

Coosystem cosm1,csm2;
cosm3 = cosm1 + csm2;
cosm2 = cosm1*2;

其中,Coosytem是结构,稍后再定义它。编译器知道它需要把两个Coosytem 结构实例加起来,即cosm1 和 cosm2。它会查找+运算符的重载,把两个Coosytem实例作为参数。果编译器找到这样的重载版本,就调用它的实现代码。如果找不到,就要看看有没有可以用作最佳匹配的其他+运算符重载,例如某个运算符重载的参数是其他数据类型,但可以隐式地转换为Coosytem实例。如果编译器找不到合适的运算符重载,就会产生一个编译错误,就像找不到其他方法调用的合适重载一样。



上了大学成了文学研究者了,一个理科生看名字报专业进了纯文科的专业(前车之鉴呢,名字好听的不一定。。),言归正传,那就用我们最熟悉的高中的立体几何只是这里创建一个Coosytem结构表示一个三维矢量,三维矢量只是三个(double)数字的一个集合,说明物体和原点之间的距离,表示数字的变量是x、y和z,x表示物体与原点在x方向上的距离,y表示它与原点在y方向上的距离,z表示高度。把这3个数字组合起来,就得到总距离。矢量可以与矢量或数字相加或相乘。如果先移动(3.0, 3.0, 1.0),再移动(2.0, –4.0, –4.0),总移动量就是把这两个矢量加起来。矢量的相加是指把每个元素分别相加,因此得到(5.0, –1.0,–3.0)。此时,数学表达式总是写成c=a+b,其中a和b是矢量,c是结果矢量。这与使用Cooystem结构的方式是一样的.下面是Cooystem的定义—— 包含成员字段、构造函数和一个ToString()重写方法,以便查看Cooystem的内容,最后是运算符重载:
struct Coosystem
{
public double x,y,z;
public Coosystem(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
public Coosystem(Coosystem com1, Coosystem com2)
{
this.x = com1.x + com2.x;
this.y = com1.y + com2.y;
this.z = com1.z + com2.z;
}
public Coosystem(Coosystem cosm)
{
this.x = cosm.x;
this.y = cosm.y;
this.z = cosm.z;
}
public override string ToString()
{
return "( "+x +","+y+","+z+")";
}
public static Coosystem operator +(Coosystem cosm1,Coosystem cosm2)
{
Coosystem result=new Coosystem(cosm1);
result.x+=cosm2.x;
result.y+=cosm2.y;
result.z+=cosm2.z;
// 等价于 Coosystem result = new Coosystem( cosm1, cosm2); return result;
}
}
下面是为+运算符提供支持的运算符重载:

C#要求所有的运算符重载都声明为public和static,这表示它们与它们的类或结构相关联,而不是与实例相关联,所以运算符重载的代码体不能访问非静态类成员,也不能访问this标识符,同时参数提供了运算符执行任务所需要知道的所有数据。前面介绍了声明运算符+的语法,下面看看重载咋运算符内部的情况:


 

{
Coosystem result=new Coosystem(cosm1);
result.x+=cosm2.x;
result.y+=cosm2.y;
result.z+=cosm2.z;
return result;
}

这部分代码与声明方法的代码是完全相同的,显然,它返回一个矢量,其中包含前面定义的cosm1和cosm2的和,即把x、y和z分别相加。再来测试一下:

 public static void Main()
{
Coosystem cosm1, cosm2, cosm3;
cosm1 = new Coosystem(2.0,2.5,5.6);
cosm2 = new Coosystem(2.3,7.2,9.8);
cosm3 = cosm1 + cosm2;
Console.WriteLine("cosm1={0}",cosm1.ToString());
Console.WriteLine("cosm2={0}", cosm2.ToString());
Console.WriteLine("cosm3={0}", cosm3.ToString());
Console.ReadKey();
}

现在继续添加更多的重载来扩展这个例子:


坐标向量除了可以相加之外,还可以相乘、相减,比较它们的值。首先重载乘法运算符,以支持标量和矢量的相乘以及矢量和矢量的相乘。矢量乘以标量只是矢量的元素分别与标量相乘。
 1    public static Coosystem operator *(double d, Coosystem co)
2 {
3 return new Coosystem(d * co.x, d * co.y, d * co.z);
4 }
5 public static Coosystem operator *( Coosystem co,double d)
6 {
7 return d*co;
8 }
9 public static double operator *(Coosystem co1, Coosystem co2)
10 {
11 return co1.x * co2.x + co1.y * co2.y + co1.z * co2.z;
12 }
上面已经编写了一个执行相乘操作的代码,所以可以重复使用该重载运算方法。 如果有两个矢量(x, y, z)和(X, Y, Z),其值就是x*X + y*Y + z*Z的值,是一个标量。虽然+=一般用作运算符,但实际上其操作分为两个部分:相加和赋值。与C++不同,C#不允许重载=运算符,但如果重载+运算符,编译器就会自动使用+运算符的重载来执行+=运算符的操作。–=、&=、*=和/=赋值运算符也遵循此规则。

 

C#中有6个比较运算符,它们分为3对:

●       == 和 !=

●       > 和 <

●       >= 和 <=

C#要求成对重载比较运算符。如果重载了==,也必须重载!=,否则会产生编译错误。另外,比较运算符必须返回bool类型的值。这是它们与算术运算符的根本区别。两个数相加或相减的结果,理论上取决于数的类型。而两个Coosystem的相乘会得到一个标量。另一个例子是.NET基类System.DateTime,两个DateTime实例相减,得到的结果不是DateTime,而是一个System.TimeSpan实例,但比较运算得到的如果不是bool类型的值,就没有任何意义。

在重载==!=时,还应重载从System.Object中继承的Equals()GetHashCode()方法,否则会产生一个编译警告。原因是Equals()方法应执行与==运算符相同的相等逻辑,除了这些区别外,重载比较运算符所遵循的规则与算术运算符相同。但比较两个数并不像想象的那么简单,例如,如果比较两个对象引用,就是比较存储对象的内存地址。比较运算符很少进行这样的比较,所以必须编写运算符,比较对象的值,返回相应的布尔结果。下面给Coosystem结构重载==和!=运算符。首先是==的执行代码:

public static bool operator ==(Coosystem co1, Coosystem co2)
{
if (ReferenceEquals(co1, co2))
return true;
if ((object)co1 == null || (object)co2==null)
return false;
return co1.x == co2.x && co1.y == co2.y && co1.z == co2.z;

}
public static bool operator !=(Coosystem co1, Coosystem co2)
{
return !(co1 == co2);
}
}

 

运算符 == 的重载中的常见错误是使用 (a == b)(a == null)(b == null) 来检查引用相等性。这会导致调用重载的运算符 ==,从而导致无限循环。应使用ReferenceEquals或将类型强制转换为 Object 来避免无限循环。

这种方式仅根据矢量组成部分的值,来对它们进行等于比较。对于大多数结构,这就是我们希望的,但在某些情况下,可能需要仔细考虑等于的含义,如果有嵌入的类,是应比较对同一个对象的引用(浅度比较),还是应比较对象的值是否相等,运算符 == 的重载中的常见错误是使用 (a == b)、(a == null) 或 (b == null) 来检查引用相等性。这会导致调用重载的运算符 ==,从而导致无限循环。应使用 ReferenceEquals 或将类型强制转换为 Object 来避免无限循环。


C# 允许用户定义的类型通过使用 operator关键字定义静态成员函数来重载运算符。但不是所有的运算符都可被重载,下表列出了不能被重载的运算符:

学习运算符重载 - 脆弱的rohelm - 脆弱的rohelm(C入门历程)

以下是测试的完整代码:

完整代码
 1 using System;
2 namespace DTGraph
3 {
4 struct Coosystem
5 {
6 public double x,y,z;
7 public Coosystem(double x, double y, double z)
8 {
9 this.x = x;
10 this.y = y;
11 this.z = z;
12 }
13 public Coosystem(Coosystem com1, Coosystem com2)
14 {
15 this.x = com1.x + com2.x;
16 this.y = com1.y + com2.y;
17 this.z = com1.z + com2.z;
18 }
19 public Coosystem(Coosystem cosm)
20 {
21 this.x = cosm.x;
22 this.y = cosm.y;
23 this.z = cosm.z;
24 }
25 public override string ToString()
26 {
27 return "( "+x +","+y+","+z+")";
28 }
29 public static Coosystem operator +(Coosystem cosm1,Coosystem cosm2)
30 {
31 Coosystem result=new Coosystem(cosm1);
32 result.x+=cosm2.x;
33 result.y+=cosm2.y;
34 result.z+=cosm2.z;
35 // 等价于 Coosystem result = new Coosystem( cosm1, cosm2);
36 //但是这样非常不好我只是简单的举例其实现方式可以不同而已
37 return result;
38 }
39 public static Coosystem operator *(double d, Coosystem co)
40 {
41 return new Coosystem(d * co.x, d * co.y, d * co.z);
42 }
43 public static Coosystem operator *( Coosystem co,double d)
44 {
45 return d*co;
46 }
47 public static double operator *(Coosystem co1, Coosystem co2)
48 {
49 return co1.x * co2.x + co1.y * co2.y + co1.z * co2.z;
50 }
51 public override bool Equals(object o)
52 {
53 Coosystem cosm = (Coosystem)o;
54 if (x == cosm.x && y==cosm.y && z==cosm.z)
55 return true;
56 return false;
57 }
58 public override int GetHashCode()
59 {
60 return (int)(x * 10 + y *10 + z * 10);
61 }
62 public static bool operator ==(Coosystem co1, Coosystem co2)
63 {
64 return co1.x == co2.x && co1.y == co2.y && co1.z == co2.z;
65 }
66 public static bool operator !=(Coosystem co1, Coosystem co2)
67 {
68 return !(co1 == co2);
69 }
70 }
71 class Cootest
72 {
73 public static void Main()
74 {
75 Coosystem cosm1, cosm2, cosm3,cosm4,cosm5,cosm6;
76 cosm1 = new Coosystem(2.0,2.5,5.6);
77 cosm2 = new Coosystem(2.3,7.2,9.8);
78 cosm6 = new Coosystem(0,0,0);
79 cosm3 = cosm1 + cosm2;
80 double param = cosm1 * cosm2;
81 cosm4= 2*cosm1;
82 cosm5 = cosm1 * 2;
83 cosm6+= cosm1;
84 Console.WriteLine("cosm1={0}",cosm1.ToString());
85 Console.WriteLine("cosm2={0}", cosm2.ToString());
86 Console.WriteLine("cosm3={0}", cosm3.ToString());
87 Console.WriteLine("cosm4={0}", cosm4.ToString());
88 Console.WriteLine(param);
89 Console.WriteLine("cosm5={0}", cosm5.ToString());
90 Console.WriteLine("cosm6={0}", cosm6.ToString());
91 Console.WriteLine("cosm1==cosm2 is {0}", cosm1 == cosm2);
92 Console.WriteLine("cosm1!=cosm2 is {0}", cosm1 != cosm2);
93 Console.ReadKey();
94 }
95 }
96 }

 

 

posted @ 2012-03-08 09:23  Halower  阅读(1927)  评论(2编辑  收藏  举报