1、第10章 运算符重载,运算符重载是对已有运算符赋予多重含义,使同一运算符作用于不同类型的数据产生不同的行为。 主要优点之一就是用户自定义数据类型能使用编译系统预定义的运算符。 10.1 运算符重载的语法 10.2 重载关系运算符 10.3 重载true、false运算符 10.4 定义转换运算符 10.5 有关复合赋值运算的说明 10.6 习题,10.1 运算符重载的语法,若要为自定义的类型重载某个运算符,就需要在该类型中定义一个方法,该方法必须命名为“operator X”,其中,operator 是运算符声明的关键字,X是想要重载的运算符的名称或符号。 例如: 定义一个表示复数的结构Com
2、plex,其中重载一个二元运算符“+”,以便能对复数变量进行相加操作。,10.1 运算符重载的语法(续),public struct Complex private double real; private double imaginary; public Complex(double real,double imaginary) this.real = real; this.imaginary = imaginary; public static Complex operator + (Complex c1, Complex c2) return new Complex(c1.real +
3、c2.real, c1.imaginary + c2.imaginary); ,10.1 运算符重载的语法(续),Complex c1 = new Complex(1, 2); Complex c2 = new Complex(3, 4); Complex sum = c1 + c2;上述第3条对两个复数进行相加的语句在编译时将自动转换成如下所述形式的对重载“+”运算符的方法的调用:Complex.operator +(c1, c2) 注意:程序中不能显式调用这个方法。,10.1 运算符重载的语法(续),运算符重载时,必须注意 用于重载运算符的方法必须是公共的和静态的,不能使用关键字virtu
4、al、abstract、override、sealed修饰。 重载一元运算符时,方法具有一个参数,重载二元运算符时,方法具有两个参数,而且在所有情况下,方法中至少有一个参数的类型与声明该运算符的类或结构的类型相同。,10.1 运算符重载的语法(续),虽然运算符重载可以改变运算符原来的行为,但是它并不能改变运算符的优先级、结合性、操作数的个数。也不能创建新的运算符,只能重载现有的运算符。例10-1 复数类型运算符重载,源代码,运 行,using System; public struct Complex private double real;private double imaginary;p
5、ublic Complex(double real, double imaginary)this.real = real;this.imaginary = imaginary;public static Complex operator +(Complex c1, Complex c2)return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);public static Complex operator -(Complex c1, Complex c2)return new Complex(c1.real - c2.r
6、eal, c1.imaginary - c2.imaginary);,public static Complex operator *(Complex c1, Complex c2)return new Complex(c1.real * c2.real - c1.imaginary * c2.imaginary,c1.real * c2.imaginary + c1.imaginary * c2.real);public static Complex operator /(Complex c1, Complex c2)return new Complex(c1.real * c2.real
7、+ c1.imaginary * c2.imaginary) / (c2.real * c2.real + c2.imaginary * c2.imaginary),(c2.real * c1.imaginary - c2.imaginary * c1.real) / (c2.real * c2.real + c2.imaginary * c2.imaginary);public override string ToString()return (real + “ + “ + imaginary + “i“); ,class TestComplex static void Main()Comp
8、lex c1 = new Complex(3, 4);Complex c2 = new Complex(1, 2);Complex sum = c1 + c2;Complex diff = c1 - c2;Complex pro = c1 * c2;Complex quot = c1 / c2;Console.WriteLine(“第一个复数是 0“, c1);Console.WriteLine(“第二个复数是 0“, c2);Console.WriteLine(“它们的和是 0“, sum);Console.WriteLine(“它们的差是 0“, diff);Console.WriteLi
9、ne(“它们的乘积是 0“, pro);Console.WriteLine(“它们的商是 0“, quot); ,课堂练习,习题 2.空间一点的坐标为(x,y,z),其中x、y、z为整数。定义一个点类Point3D,并重载“+”和“-”运算符为对应3个坐标分别进行加减。,using System; class Point3D private readonly int x;private readonly int y;private readonly int z;public Point3D(int x, int y,int z)this.x = x;this.y = y;this.z = z;
10、public static Point3D operator +(Point3D p1, Point3D p2)return new Point3D(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);public static Point3D operator -(Point3D p1, Point3D p2)return new Point3D(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);public override string ToString()return (“( “ + x + “ , “ + y + “ , “ +
11、z + “ )“); ,课堂练习(续),class TestPoint static void Main()Point3D p1 = new Point3D(1, 2,3);Point3D p2 = new Point3D(3, 4,5);Console.WriteLine(“第一个点是 0“, p1);Console.WriteLine(“第二个点是 0“, p2);Console.WriteLine(“这两个点和是0n“, p1+p2 );Console.WriteLine(“这两个点差是0n“, p1 - p2); /*/,10.1 运算符重载的语法(续),可以重载的运算符: 一元运算符
12、:+、-、!、+、-、true 、false二元运算符:+、-、*、/、%、&、|、=、!=、= 不可重载的运算符:&、|、()、+=、 -=、*=、 /=、 %=、&=、|=、 =、=、=、.、?:、new、is、sizeof、 typeof,10.2 重载关系运算符,关系运算符重载必须成对进行。也就是说,如果重载 =,也必须重载 !=。反之亦然, 以及 = 同样如此。 默认情况下,运算符“=”和“!=”不能作用于自定义的结构变量,但可作用于类的对象。作用于类的对象时,是通过判断两个对象是否引用同一对象来测试引用是否相等,不是比较对象所包含的值是否相等。 一般情况在,重载运算符“=”和“!=
13、”时,还应同时重写从System.Object类中继承来的方法Equals和GetHashCode。不重写,编译器会给出警告信息。例10-2 关系运算符重载,源代码,运 行,using System; class Point private readonly int x;private readonly int y; public Point(int x, int y) this.x = x; this.y = y; public override bool Equals(object obj) /重写object类中的方法 if (obj = null) return false; /如果参
14、数为空,返回falsePoint p = obj as Point; if (object)p = null) return false; /如果参数不能转换为Point,返回falsereturn (x = p.x) ,public override int GetHashCode() /重写object类中的方法 return x y; public static bool operator =(Point a, Point b) if (object.ReferenceEquals(a, b) /如果两个参数都为空或是同一对象,返回truereturn true; /如果两个参数中只有一
15、个为空,返回falseif (object)a = null) | (object)b = null)return false; return a.x = b.x ,class TestPoint static void Main() Point p1 = new Point(1, 2); Point p2 = new Point(3, 4); Console.WriteLine(“第一个点是 0“, p1); Console.WriteLine(“第二个点是 0“, p2); Console.WriteLine(“这两个点0同一位置n“,(p1=p2)?“在“:“不在“);Point p3
16、= new Point(1, 2); Console.WriteLine(“第一个点是 0“, p1); Console.WriteLine(“第二个点是 0“, p3); Console.WriteLine(“这两个点0同一位置“,(p1!=p3)?“不在“:“在“); ,课堂练习,习题 4.为习题2中定义的点类Point3D重载“=”运算符以实现两个空间点的关系运算。,课堂练习(续),class Point3D private readonly int x;private readonly int y;private readonly int z;public Point3D(int x,
17、 int y, int z)this.x = x;this.y = y;this.z = z;public static Point3D operator +(Point3D p1, Point3D p2)return new Point3D(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);public static Point3D operator -(Point3D p1, Point3D p2)return new Point3D(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);,public override bool Equ
18、als(object obj) /重写object类中的方法if (obj = null) /如果参数为空,返回falsereturn false;Point3D p = obj as Point3D;if (object)p = null) /如果参数不能转换为Point3D,返回falsereturn false;return (x = p.x) ,public static bool operator =(Point3D a, Point3D b)if (object.ReferenceEquals(a, b) /如果两个参数都为空或是同一对象,返回truereturn true;/如果
19、两个参数中只有一个为空,返回falseif (object)a = null) | (object)b = null)return false;return a.x = b.x ,class TestPoint static void Main()Point3D p1 = new Point3D(1, 2, 3);Point3D p2 = new Point3D(3, 4, 5);Console.WriteLine(“第一个点是 0“, p1);Console.WriteLine(“第二个点是 0“, p2);Console.WriteLine(“这两个点和是0n“, p1 + p2);Con
20、sole.WriteLine(“这两个点差是0n“, p1 - p2);Point3D p3 = new Point3D(3, 4, 5);Console.WriteLine(“这两个点p1,p30同一位置“, (p1 = p3) ? “在“ : “不在“);Console.WriteLine(“这两个点p2,p30同一位置“, (p2 = p3) ? “在“ : “不在“);Point3D p5 = null, p4 = null;Console.WriteLine(“这两个点p4,p50同一位置“, (p4 = p5) ? “在“ : “不在“); ,10.3 重载true、false运算
21、符,关键字true和false可用作运算符。不过,这两个运算符相当特别,一般只用于运算符重载。 当用户自定义某个类或结构时,如果希望它能用于表示true或false,那么在该类型中重载这两个运算符就很有意义。,10.3 重载true、false运算符(续),重载这两个运算符时,应该注意: 方法的返回值类型必须是bool。 这两个运算符必须同时重载。 重载运算符true时,返回布尔值 true以表示真,否则返回 false;重载运算符false时,返回布尔值 true 以指示表示假,否则返回 false。例10-3 重载运算符true和false,源代码,运 行,using System; pu
22、blic struct Prime private uint value;public Prime(uint value) this.value = value; public static bool operator true(Prime p) return IsPrime(p.value); public static bool operator false(Prime p) return !(IsPrime(p.value); public static bool IsPrime(uint value)for (uint i = 2; i = value / 2; i+)if (valu
23、e % i = 0) return false;return true;,public override string ToString()return (“ + value); class TestPrime static void Main()for (uint i = 2; i 20; i+)Prime p = new Prime(i);if (p)Console.Write(p + “ “);Console.WriteLine(); ,10.4 定义转换运算符,C#语言中,不能重载转换运算符“()”,但允许在类和结构中定义新的转换运算符,以便类或结构与其他类、结构或者基本数据类型之间进
24、行相互转换。 转换运算符的定义方法类似于运算符重载,方法同样命名为“operator X”,不过,X应为它想要转换到的类型名。 例如:在ExampleClass定义一个转换运算符,使用它可以将一个int型变量转换成ExampleClass型。,10.4 定义转换运算符(续),class ExampleClass private int i;public ExampleClass(int i) this.i=i; public static explicit operator ExampleClass(int i) return new ExampleClass(i); 关键字explicit表
25、示这种转换必须强制进行,例如: int i=2; ExampleClass e2=(ExampleClass)i;,10.4 定义转换运算符(续),定义转换运算符时,必须注意: 定义转换运算符的方法必须是公共和静态的。 方法必须有一个参数,该参数的类型为需要转换的类型。 不能为方法指定返回值类型,void也不可以。 方法的参数和想要转换到的类型中必须有一个与定义该运算符的类或结构的类型相同,但不能两个都相同,即用户自定义类型转换只能转换或转换为定义该转换运算符的当前数据类型,而且不允许从一种类型转换到同一类型。,10.4 定义转换运算符(续),用户自定义类型转换不允许从一种类型转换到其派生类或
26、基类,也不能转换或转换为接口类型。 定义转换运算符的方法必须使用关键字implicit 或explicit修饰。其中,声明为 implicit 的转换在需要时可以自动进行,即该运算符是一个隐式转换运算符;声明为 explicit 的转换需要使用强制转换,即该运算符是一个显式转换运算符。,10.4 定义转换运算符(续),如果某个转换操作总是安全的,从不引发异常,也从不丢失信息,就应该将运算符声明为隐式的,否则就应该将运算符声明为显式的,以防止编译器无提示地调用可能产生无法预见后果的转换操作。例10-4 定义转换运算符,源代码,运 行,using System; struct DecNumber
27、private int value;public DecNumber(int value) this.value = value; static public implicit operator DecNumber(int value) return new DecNumber(value); static public implicit operator DecNumber(HexNumber hex) return new DecNumber(int)hex); / 从HexNumber到int型显式转换 static public explicit operator int(DecNum
28、ber dec) return dec.value; static public implicit operator string(DecNumber dec) return “ + dec.value; ,struct HexNumber private int value;public HexNumber(int value) this.value = value; static public implicit operator HexNumber(int value) return new HexNumber(value); static public explicit operator
29、 HexNumber(DecNumber dec)return new HexNumber(int)dec); / 从DecNumber到int型显式转换static public explicit operator int(HexNumber hex) return (hex.value); static public implicit operator string(HexNumber hex)return “没有提供实现代码“; ,class TestConversions static void Main()DecNumber dec;HexNumber hex;dec = 11; /
30、 从int到DecNumber型隐式转换Console.WriteLine(dec); / 从DecNumber到string型隐式转换hex = (HexNumber)dec; / 从DecNumber到HexNumber型显式转换Console.WriteLine(hex); / 从HexNumber到string型隐式转换Console.WriteLine(int)hex); / 从HexNumber到int型显式转换 hex = 22; / 从int到HexNumber型隐式转换dec = hex; / 从HexNumber到DecNumber型隐式转换Console.WriteLin
31、e(dec); ,10.5 有关复合赋值运算的说明,所有赋值运算符都不能重载,但复合赋值运算符总是使用与它关联的运算符来求值的。 例如,运算符“+=”总是使用“+”来计算。 因此,如果重载了某个运算符,就可以进行与之相关的赋值运算。 例如:,10.5 有关复合赋值运算的说明,public struct Complex private double real; private double imaginary; public Complex(double real, double imaginary) this.real = real; this.imaginary = imaginary; p
32、ublic static Complex operator +(Complex c1, Complex c2) return new Complex(c1.real + c2.real,c1.imaginary + c2.imaginary); public static implicit operator Complex(double value) return new Complex(value, 0); ,10.5 有关复合赋值运算的说明(续),结构Complex重载了运算符“+”,因此下述语句合法Complex c1 = new Complex(3, 4); Complex c2 = new Complex(1, 2); c2 += c1 ;其中,第3条语句将自动使用下述语句进行计算:c2= c2 + c1 ;,10.5 有关复合赋值运算的说明(续),结构Complex中定义一个将double型隐式转换成Complex型的转换运算符,因此下述语句合法double d = 2.1;/计算时,d中的数据隐式转换成Complex型c1 +=d;/无法将一个Complex型数据转换成double型,/下述语句是非法的d += c1;,10.6 习 题,1.C#语言中,运算符重载需要使用什么关键字? operator,10.6 作业,3.定义一个复数类,并为它重载自增自减运算符。,