运算符重载
Eric Gunnerson
Microsoft Corporation
2001年6月21日 作为有关 C# 语言规范漫谈的继续,本月我们将讨论运算符重载的问题。运算符重载(除非特别指明,否则本专栏的其余部分一律将其简称为“重载”)是指允许用户使用用户定义的类型编写表达式的能力。它允许用户定义的类型与预定义的类型具有相同的功能。
例如,通常需要编写类似于以下内容的代码,以将两个数字相加。很明显,sum 是两个数字之和。
如果可以使用代表复数的用户定义的类型来编写相同类型的表达式,那当然是最好不过了:
运算符重载允许为用户定义的类型重载(即指定明确的含义)诸如“+”这样的运算符。如果不进行重载,则用户需要编写以下代码:
此代码可以很好地运行,但 Complex 类型并不能象语言中的预定义类型那样发挥作用。
在这一示例中,重载的使用也没有使代码变得更简单。如果我们转而使用以下代码,情况就好多了:
为了演示重载,我们将开发一个矢量。矢量可以被认为是从原点到特定二维点的线。可以对矢量执行多种运算。以下是该类型的粗略定义:
要实际使用,矢量应支持以下运算: 获取长度 将矢量乘以某个数字 将矢量除以某个数字 将两个矢量相加 将一个矢量减去另一个矢量 计算两个矢量的点积 我们的任务是确定应该如何实现这些运算。
应该注意,此处有许多有趣的现象。首先,运算符是 static 函数,因此它必须获取两个参数的值,同时在结果中必须返回一个新的对象。运算符的名称恰好是“operator”,后面紧跟着要重载的运算符。
除以某个数字的代码与以上代码类似。
减法的代码与以上代码非常类似。
此时,它几乎是一个判断调用。我编写的类对“*”运算符进行了重载,以便进行点积运算,但回过头细想一下,我认为这一代码并不是最合适的代码。
在第一个示例中,velocity 和 center 是矢量这一点并不是很清晰,因此,点积是要执行的运算这一点也不是很清晰(我在查找一个使用它的示例时,注意到了这一点)。第二个示例很清楚地说明了要执行什么运算,我认为使用该示例中的代码最合适。
第三个示例也还可以,但我认为,如果该运算不是成员函数的话,代码会更清晰一些。
与 C++ 相比较,C# 允许重载的运算符很少。有两条限制。首先,成员访问、成员调用(也就是函数调用)、赋值以及“新建”无法重载,因为这些运算是运行时定义的。
其次,诸如“&&”、“||”、“?:”这样的运算符以及诸如“+=”这样的复合赋值运算符无法重载,因为这会使代码变得异常复杂,得不偿失。
虽然知道了如何重载加法运算符,但我们仍需要想方法使第一个语句发挥作用。这可以通过对转换进行重载来实现。
显式转换是那些可能导致数据丢失或者引发异常的转换。因此,显式转换要求强制进行类型转换:
对转换进行重载时,应该决定转换是隐式还是显式的,但是,应该明白隐式转换模型是安全的,而显式转换则是有风险的。
将整数值 5 转换为复数的转换定义如下所示:
这允许进行从 int 到 Complex 的隐式转换。
运算符重载不是 .NET 公共语言子集中的功能之一,这意味着在某些语言中将无法使用重载。因此,提供非重载的替代方案是非常重要的,以便在其它语言中仍然能执行相同的运算。如果您的类定义了加法运算符,它还应该定义相同的方法,使用类似 Add 这样的名称进行命名。
本月只提供了一个站点:
http://www.cshrp.net/