介绍 C# 中的运算符重载 .

摘要:
介绍C#中重载Zhou Rong的运算符。版权所有2007年5月至2007年。本文介绍了一种新的重载模型——运算符重载。事实上,提出了运算符重载来解决这些问题。CLR框架下的各种CLR语言在不同程度上支持运算符重载。让我们通过介绍复杂类的定义来深入了解C#语言中的运算符重载。C#运算符重载决策示例下面的示例定义了一个Complex类,它实现了四个复杂的加法、减法、乘法和除法运算。
介绍 C# 中的运算符重载
周融,2007 年 5 月
(C) 2001-2007 保留所有权利。

重载是面向对象中的一个重要概念,它是对象多态性的一种不完全体现,人们通常所说的重载,往往指的是函数的重载。本文向读者介绍一种新的重载模型——运算符重载。

在本文中的内容:
1、为什么需要运算符重载
2、C# 运算符重载决策示例
3、C# 运算符重载一览表
4、结论

为什么需要运算符重载

函数的重载为一个对象上的相同行为提供不同的参数方式,这样,开发人员便可以使用这些不同的参数实现类似的功能。一组函数重载决策一般实现的功能是相同的,例如对 Object 对象上的 ToString() 方法就有几个重载版本,虽然它们接受的参数不同,但却都表达同一个行为的最终结果。参数的不同导致函数重载版本的签名不同,这样编译器很容易知道需要调用那一个重载版本。这种技术给开发人员带来了方便。

现在我们试图对重载的定义进行推广。先让我们看看最简单的例子,我们通常需要像这样声明并初始化一个值类型的变量:

介绍 C# 中的运算符重载 .第1张int digit = 5;
介绍 C# 中的运算符重载 .第1张
string sayHello = "Hello, World";

这里的“=”运算符,就是将右边的值传递给左边变量的赋值运算符。这里,5 的类型为 int,“Hello, World”的类型为 string,这与左边被赋值的变量类型完全一致。

但对于上述的解释,我们还可以这样认为:5 的类型为 uint 或 byte,"Hello, World"的类型为 char[],那么如此一来,赋值运算左边和右边的类型就不在等同,那么编译器如何处理呢?有人会说,这就是“隐式类型转换”,这个答案确实很好,但隐式类型转换的规则已经被编译器确定,如果赋值运算的两端不遵循隐式类型转换规则,则需要显式类型转换,例如:

介绍 C# 中的运算符重载 .第1张char c = '2';
介绍 C# 中的运算符重载 .第1张
string s = (string)c;
介绍 C# 中的运算符重载 .第1张
int i = (int)c;

这些显式类型转换并不适用于任何场合,也许人们希望在其自定义的类中也能用赋值、加减乘除等语法操作它们。

对象和对象之间是可能存在这种特殊的运算关系的,一个典型的例子就是“复数”对象。复数是一个值类型对象,它包含两个 double 类型的域,两个复数对象可以被加、减、乘、除和相等性判断,但无法比较大小。我们试想可以如此操作复数类:

介绍 C# 中的运算符重载 .第1张Complex c1, c2;
介绍 C# 中的运算符重载 .第1张c1 
= new Complex(34);
介绍 C# 中的运算符重载 .第1张c2 
= "4+5i";
介绍 C# 中的运算符重载 .第1张var c3 
= c1 * c2 / -c1 + c2;
介绍 C# 中的运算符重载 .第1张
if (c1 == c2) c3 = c1; else c3 = c2;

我们可以从这些代码可以预见运算符重载所需要实现的功能:
1、支持隐式类型转换和显式类型转换的重载决策。
2、支持基本二元运算符,如加、减、乘、除等。
3、支持基本一元运算符,如取负、取反、自增、自减等。
4、支持基本关系运算符,如大于、小于、等于和不等于等。
5、实现更加复杂的运算符,如三元运算、[]、()、位运算等。

事实上,运算符重载的提出就是为了解决这些问题。在 CLR 框架下的各种 CLR 语言,均不同程度的支持运算符重载。Visual Basic 在 8.0 版本上(也就是 Visual Studio 2005)也支持了运算符重载,运算符重载除了以上列出的优点外,还具备如下特点。
1、使得代码更加容易理解和阅读。
2、可以利用现有运算符的优先级关系规则处理对象之间的运算。
3、使得代码更加灵活,对象可操作性更好。
4、开发人员可以举一反三,因为他们熟悉了常规值类型上的运算符使用,这样可以直接将这些规则引入到自定义对象上。

下面我们通过介绍复数类的定义,来深入 C# 语言中的运算符重载。

C# 运算符重载决策示例

下面的例子定义一个 Complex 类,实现了复数加、减、乘和除的四则运算。C# 中定义常规运算符的语法如下:

[public | private | protected | internal | internal protected] static <return type> | implicit | explicit operator <operatorName> ( <param list> )

下面是 C# 3.0 代码。

介绍 C# 中的运算符重载 .第1张    struct Complex
介绍 C# 中的运算符重载 .第12张介绍 C# 中的运算符重载 .第13张    
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
public double Real ...getset; }
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
public double Imaginary ...getset; }
介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
public Complex(double real, double imaginary) : this() ...this.Real = real; this.Imaginary = imaginary; }
介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第18张        
public static Complex operator +(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = c1.Real + c2.Real, Imaginary = c1.Imaginary + c2.Imaginary };
介绍 C# 中的运算符重载 .第27张        }

介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第18张        
public static Complex operator -(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = c1.Real - c2.Real, Imaginary = c1.Imaginary - c2.Imaginary };
介绍 C# 中的运算符重载 .第27张        }

介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第18张        
public static Complex operator *(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = c1.Real * c2.Real - c1.Imaginary * c2.Imaginary, Imaginary = c1.Real * c2.Imaginary 
介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第27张
- c1.Imaginary * c2.Real }
;
介绍 C# 中的运算符重载 .第27张        }

介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第18张        
public static Complex operator /(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = -c1.Real * c2.Real + c1.Imaginary * c2.Imaginary, Imaginary = -c1.Real * 
介绍 C# 中的运算符重载 .第18张
介绍 C# 中的运算符重载 .第27张c2.Imaginary 
+ c1.Imaginary * c2.Real }
;
介绍 C# 中的运算符重载 .第27张        }

介绍 C# 中的运算符重载 .第53张    }

由于运算符重载定义在定义它的对象实例上生效,所以可以改写 operator - 和 operator / 运算,使其更加简单。       

介绍 C# 中的运算符重载 .第1张        public static Complex operator -(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第12张介绍 C# 中的运算符重载 .第13张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return c1 + new Complex ...{ Real = c2.Real, Imaginary = c2.Imaginary };
介绍 C# 中的运算符重载 .第53张        }

介绍 C# 中的运算符重载 .第1张
介绍 C# 中的运算符重载 .第1张        
public static Complex operator /(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第12张介绍 C# 中的运算符重载 .第13张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return c1 * new Complex ...{ Real = -c2.Real, Imaginary = -c2.Imaginary };
介绍 C# 中的运算符重载 .第53张        }

介绍 C# 中的运算符重载 .第1张
介绍 C# 中的运算符重载 .第1张

这样我们就可以很方便的使用 Complex 类:

介绍 C# 中的运算符重载 .第1张var c1 = new Complex(34), c2 = new Complex(12);
介绍 C# 中的运算符重载 .第1张var c3 
= c1 * c2;
介绍 C# 中的运算符重载 .第1张Complex c4 
= c1 - c2 / c3 + c1;

为了实现更加简单的赋值,我们还需要实现隐式类型转换(从 string 到 Complex)。

介绍 C# 中的运算符重载 .第1张        public static implicit operator Complex(string value)
介绍 C# 中的运算符重载 .第12张介绍 C# 中的运算符重载 .第13张        
...{
介绍 C# 中的运算符重载 .第18张            value 
= value.TrimEnd('i');
介绍 C# 中的运算符重载 .第18张            
string[] digits = value.Split('+''-');
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = Convert.ToDouble(digits[0]), Imaginary = Convert.ToDouble(digits[1]) };
介绍 C# 中的运算符重载 .第53张        }

介绍 C# 中的运算符重载 .第1张
介绍 C# 中的运算符重载 .第1张

可以通过如下代码对实例进行赋值。

介绍 C# 中的运算符重载 .第1张Complex c = "4+5i";

在编译器生成这些运算符重载代码时,实际上会为每个已被重载运算符定义一个特殊名称的方法。如 operator +,其实等同于如下代码:

介绍 C# 中的运算符重载 .第1张        [SpecialName]
介绍 C# 中的运算符重载 .第1张        
public static Complex op_Addition(Complex c1, Complex c2)
介绍 C# 中的运算符重载 .第12张介绍 C# 中的运算符重载 .第13张        
...{
介绍 C# 中的运算符重载 .第14张介绍 C# 中的运算符重载 .第15张            
return new Complex ...{ Real = c1.Real + c2.Real, Imaginary = c1.Imaginary + c2.Imaginary };
介绍 C# 中的运算符重载 .第53张        }

C# 运算符重载一览表

您可以在 C# 中对于以下运算符进行重载决策。

+, -, !, ~, ++, --, true, false
 这些一元运算符可被重载。
 
+, -, *, /, %, &, |, ^, <<, >>
 这些二元运算符可被重载。
 
==, !=, <, >, <=, >=
 这些关系运算符可被重载。
 
&&, ||
 这些条件运算符不能被重载,但它们的值被 & 和 | 评估,而这两个运算符可以重载。
 
[]
 数组运算符不能被重载,但您可以定义索引器。
 
()
 转换运算符不能被重载,但您可以定义隐式类型转换和显式类型转换运算符。
 
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
 这些赋值运算符不能被重载,但他们的值,如 +=,会被 + 评估,而 + 可以被重载。
 
=, ., ?:, ->, new, is, sizeof, typeof
 这些运算符不能被重载。

结论

运算符重载是对重载概念的一个重要补充和发展,它针对对象关系中的多元关系和四则运算、关系运算等常规运算提供了重载支持,开发人员可以利用运算符重载优化利用到这些关系的实现代码中,以提高生产力。

免责声明:文章转载自《介绍 C# 中的运算符重载 .》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇linux Samba 搭建Docker 镜像加速下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

QT父子窗口事件传递与事件过滤器(讲了一些原理,比较清楚)

处理监控系统的时候遇到问题,在MainWidget中创建多个子Widget的时候,原意是想鼠标点击先让MainWidget截获处理后再分派给子Widget去处理,但调试后发现如果子Widget重新实现了事件方法,就直接处理掉事件了,没有进到MainWidget的处理方法中去,如果子Widget没有accept或ignore该事件,则该事件就会被传递给其父亲...

Array和ArrayList的Clone为什么一个不用类型转换,一个要类型转换

通过上面一段代码可以看出Array的Clone()不用进行类型转换,但ArrayList的Clone要进行类型转换。为什么会出现这种情况呢?我们来分析下源码 现在来看下Array的Clone()方法源码 发现Array里没有Clone()方法,调用的都是Object里的方法 这里面的除了Object并不是说Object就不遵守这个惯例,而是Objec...

【三思Unity】深入浅出资源管理之:Asset Import Pipeline v2(上)

每个游戏都是由种类繁多的资源构成,例如:网格、材质、纹理、着色器、动画、音频等。导入并管理这些资源文件,是游戏引擎必备的能力。资源管理包含两部分:离线管理和运行时管理。 在 Unity 2019 LTS 中,对应的解决方案分别是 Asset Import Pipeline v2(下文简称 AIP v2) 和 Addressable Asset System...

C# 反射给对象赋值遇到的问题——类型转换

反射给对象赋值遇到的问题——类型转换 给一个对象属性赋值可以通过PropertyInfo.SetValue()方式进行赋值,但要注意值的类型要与属性保持一致。    创建对象实例的两种方法:  1. 1 var obj = Assembly.Load("AssemblyName").CreateInstance("AssemblyName"+"Cla...

【转】C#中Graphics的画图代码

C#中Graphics的画图代码【转】 架上图片了你就可以在画板上涂改了啊我要写多几个字上去string str = "Baidu"; //写什么字?Font font = Font("宋体",30f); //字是什么样子的?Brush brush = Brushes.Red; //用红色涂上我的字吧;PointF point = new PointF(1...

数据库日期类型转换–HSQL

最近遇到要用HSQL查询离某个时间的后十分钟的记录,不像Oracle和SqlServer中可以直接有函数转换,而是直接通过'+'来得到 Hsql Document -- standard forms CURRENT_DATE + '2' DAY SELECT (LOCALTIMESTAMP - atimestampcolumn) DAY TO S...