运算符重载是指允许用户使用用户定义的类型编写表达式的能力。它允许用户定义的类型与预定义的类型具有相同的功能。
例如,通常需要编写类似于以下内容的代码,以将两个数字相加。很明显,sum 是两个数字之和。
int i = 5;
int sum = i + j;
如果可以使用代表复数的用户定义的类型来编写相同类型的表达式,那当然是最好不过了:
Complex i = 5;
Complex sum = i + j;
运算符重载允许为用户定义的类型重载(即指定明确的含义)诸如“+”这样的运算符。如果不进行重载,则用户需要编写以下代码:
Complex i = new Complex(5);
Complex sum = Complex.Add(i, j);
此代码可以很好地运行,但 Complex 类型并不能象语言中的预定义类型那样发挥作用。
任何事情都有特定的时间和场所
运算符重载是一个容易引起误解的语言功能,而且编程人员对待它的态度也大相径庭。一些人认为:用户使用这一功能编写的程序将令人费解,而且它也不应归于编程语言。另一些人则认为它是一个很不错的功能,在任何地方都可以使用。
这两种观点既包含正确的成分,但也有欠妥之处。应该承认,运算符重载可能会导致编写出的程序令人费解,但根据我的经验,即使不使用运算符重载,也很可能编写出令人费解的代码。在某些情况下,不使用重载甚至会使代码更加令人费解。
那些不分场合、随意使用重载的人“确实”在生产令人费解的代码。
在语言中之所以使用重载,是为了在概念上对用户的类或结构进行简化。只有在有助于提高用户所写代码的可读性时,才能对运算符进行重载。请注意,我们所说的检验标准是“更清晰”,而不是“更简短”。运用了运算符重载的类几乎总是会使代码变得更简短,但并不能每次都使代码变得更清晰(即可读性更强)。
为了说明这一点,我创建了多个重载示例。您需要仔细阅读这些代码,想一想哪个运算符进行了重载,重载的运算符执行了什么运算。
测验
1
BigNum n1 = new BigNum("123456789012345");
BigNum n2 = new BigNum("11111");
BigNum sum = n1 + n2;
B
Matrix m1 = loadMatrix();
Matrix m2 = loadMatrix();
Matrix result = m1 * m2;
iii
DBRow row = query.Execute();
while (!row.Done)
{
Viewer.Add(row);
row++;
}
IV
Account current = findAccount(idNum);
current += 5;
答案和讨论
1
本示例中,要执行的运算是显而易见的。这种加法只不过是将预定义的类型相加,每个人都明白执行了什么运算,因此在这个示例中,使用运算符重载很有意义。
B
本示例演示了矩阵如何相乘。从概念上来说,矩阵乘法与常规乘法不完全类似,但它是一个明确定义的运算,因此任何理解矩阵乘法的人看到这种重载的运算符时,都不会感到惊讶。
iii
本示例中,增量 (++) 运算符进行了重载,它使数据库行向前移至下一行。任何与数据库行有关的事物都不可能使我们理解这种增量的真正含义,而且,这种增量要执行的运算也不是那么明显。
在这一示例中,重载的使用也没有使代码变得更简单。如果我们转而使用以下代码,情况就好多了:
DBRow row = query.Execute();
while (!row.MoveNext())
{
Viewer.Add(row);
}
IV
将事物和雇员相加代表什么含义呢?本示例中,选择是一个不错的方法,将其与雇员数相加就会注册雇员。这是一种很糟糕的运算符重载用法。
原则
何时进行重载的原则是相当简单的。如果用户希望能执行这种运算,那么就应该进行重载。
重载算术运算符
要重载 C# 中的运算符,指定要执行运算的函数就可以了。函数必须在运算所涉及的类型中进行定义,并且至少有一个参数属于该类型。这样可以防止对 int 的加法或其它奇怪事物进行重载。
为了演示重载,我们将开发一个矢量。矢量可以被认为是从原点到特定二维点的线。可以对矢量执行多种运算。以下是该类型的粗略定义:
struct Vector
{
float x;
float y;
public Vector(float x, float y)
{
this.x = x;
this.y = y;
}
}
要实际使用,矢量应支持以下运算:
获取长度
将矢量乘以某个数字
将矢量除以某个数字
将两个矢量相加
将一个矢量减去另一个矢量
计算两个矢量的点积
我们的任务是确定应该如何实现这些运算。
长度
对于获取矢量的长度,似乎没有任何有意义的运算符。长度不会变化,因此将它作为属性是很有意义的:
public float Length
{
get
{
return((float) Math.Sqrt(x * x + y * y));
}
}
将矢量乘以/除以某个数字
将矢量乘以某个数字是相当常见的运算,并且是用户希望实现的运算。以下是相关代码:
public static Vector operator*(Vector vector, float multiplier)
{
return(new Vector(vector.x * multiplier,
vector.y * multiplier));
}
应该注意,此处有许多有趣的现象。首先,运算符是 static 函数,因此它必须获取两个参数的值,同时在结果中必须返回一个新的对象。运算符的名称恰好是“operator”,后面紧跟着要重载的运算符。
除以某个数字的代码与以上代码类似。
将两个矢量进行加减
这是很常见的矢量运算,因此很显然要对它们进行重载。
public static Vector operator+(Vector vector1, Vector vector2)
{
return(new Vector(vector1.x + vector2.x,
vector1.y + vector2.y));
}
减法的代码与以上代码非常类似。
计算点积
两个矢量的点积是为矢量定义的特殊运算,在预定义的类型中根本无法找到与之相类似的运算。在方程式中,点积通过在两个矢量之间写一个点来表示,因此它和任何现有运算符都不是精确匹配。点积的一个有趣特征是:它获取两个矢量的值,但只返回一个简单的数字。
无论是否对该运算进行重载,用户代码都大致相同。第一行显示了正在使用的重载版本,其它行则显示了两个替代版本:
double v1i = (velocity * center) / (t * t);
double v1i = Vector.DotProduct(velocity, center) / (t * t);
double v1i = velocity.DotProduct(center) / (t * t);
此时,它几乎是一个判断调用。我编写的类对“*”运算符进行了重载,以便进行点积运算,但回过头细想一下,我认为这一代码并不是最合适的代码。
在第一个示例中,velocity 和 center 是矢量这一点并不是很清晰,因此,点积是要执行的运算这一点也不是很清晰(我在查找一个使用它的示例时,注意到了这一点)。第二个示例很清楚地说明了要执行什么运算,我认为使用该示例中的代码最合适。
第三个示例也还可以,但我认为,如果该运算不是成员函数的话,代码会更清晰一些。
public static double DotProduct(Vector v1, Vector v2)
{
return(v1.x * v2.x + v1.y * v2.y);
}
C# 和 C++ 重载
与 C++ 相比较,C# 允许重载的运算符很少。有两条限制。首先,成员访问、成员调用(也就是函数调用)、赋值以及“新建”无法重载,因为这些运算是运行时定义的。
其次,诸如“&&”、“||”、“?:”这样的运算符以及诸如“+=”这样的复合赋值运算符无法重载,因为这会使代码变得异常复杂,得不偿失。
重载的转换
让我们返回到最初的示例:
Complex i = 5;
Complex sum = i + j;
虽然知道了如何重载加法运算符,但我们仍需要想方法使第一个语句发挥作用。这可以通过对转换进行重载来实现。
隐式和显式转换
C# 同时支持隐式和显式转换。隐式转换是那些总是能成功执行的转换,并且其成功的原因通常是目标类型的范围等于或大于源类型的范围。从 short 到 int 的转换就是一个隐式转换。隐式转换可以作为赋值语句的一部分:
short svalue = 5;
long lvalue = svalue;
显式转换是那些可能导致数据丢失或者引发异常的转换。因此,显式转换要求强制进行类型转换:
long lvalue = 5;
short svalue = (short) lvalue;
对转换进行重载时,应该决定转换是隐式还是显式的,但是,应该明白隐式转换模型是安全的,而显式转换则是有风险的。
将整数值 5 转换为复数的转换定义如下所示:
public static implicit operator Complex(int value)
{
return(new Complex(value, 1.0));
}
这允许进行从 int 到 Complex 的隐式转换。
语言的互操作性
以上是在 C# 中对运算符进行重载的情况。涉及到其它语言时,事情将变得略为复杂。
运算符重载不是 .NET 公共语言子集中的功能之一,这意味着在某些语言中将无法使用重载。因此,提供非重载的替代方案是非常重要的,以便在其它语言中仍然能执行相同的运算。如果您的类定义了加法运算符,它还应该定义相同的方法,使用类似 Add 这样的名称进行命名。
分享到:
相关推荐
C#之运算符重载 C#语言中允许用户根据所创建的类来定义运算符的含义,该过程称为运算符重载。通过重载运算符,可以扩展运算符在类中的作用。运算符重载的优点是:它允许将新的类类型无缝隙地整合进用户自己的编程...
以下是一些关于C#运算符重载的关键点: 1. **语法**:重载运算符的定义通常包括返回类型、`operator`关键字、要重载的运算符符号以及参数列表。例如,对于加法运算符`+`,定义如下: ```csharp public static Box ...
本文实例讲述了C#运算符重载用法。分享给大家供大家参考。具体分析如下: public class Plane { public virtual double TopSpeed() { return 300.0D;} public static bool operator>(Plane one, Plane two) { ...
在C#编程语言中,运算符重载是一种特殊的技术,允许程序员为自定义类型赋予标准运算符的新含义。当我们创建一个表示复数的类时,为了使得复数的加法、减法、乘法和除法等操作直观易用,就需要重载相应的运算符。复数...
在本节“VS2010轻松学习C# - 从零到深入 - 天轰穿.NET4趣味编程视频教程_第16讲:运算符重载”中,天轰穿老师将引领我们深入理解C#中的运算符重载概念。运算符重载是面向对象编程的一个重要特性,允许程序员为自定义...
2. **索引器**:虽然不能直接重载数组索引运算符,但可以通过定义索引器实现类似的功能。索引器允许类型使用类似于数组索引的方式访问数据: ```csharp Hashtable h = new Hashtable(); // .NET Framework 的哈希...
运算符重载是C#(以及其他面向对象语言)中的一项特性,它允许程序员为自定义类型赋予特定的运算符含义,使得使用这些类型的代码更加直观和简洁。在讲解运算符重载之前,我们先理解一下自定义类型的概念。自定义类型...
天轰穿系列教程之-17重载方法以及运算符重载天轰穿系列教程之-17重载方法以及运算符重载天轰穿系列教程之-17重载方法以及运算符重载天轰穿系列教程之-17重载方法以及运算符重载天轰穿系列教程之-17重载方法以及...
在C#编程语言中,复数类是一种表示复数数据类型的标准,它包含实部和虚部。复数在数学中广泛使用,特别是...在学习过程中,你可以通过编写测试用例来验证这些运算符重载的正确性,以确保它们符合数学上的复数运算规则。
本文将深入探讨如何在C#中实现运算符重载,以及注意事项和示例。 首先,理解为什么要重载运算符。在C#中,标准运算符如`+`、`-`、`*`、`/`通常用于基本数据类型,如整数、浮点数或字符串。然而,当我们创建自己的...
定义复数类Complex,实现复数的+、-、*、/数学运算,=、+=、-=、*=、/=赋值计算,++a、a++、--a、a—自增自减,==、!=关系运算
在C#编程语言中,运算符重载是一种特殊的技术,允许程序员为自定义类型赋予标准的数学或逻辑运算符新的含义。这使得我们能够用自然、直观的方式操作自定义对象,比如自定义的数字类型或者复数类型。下面将详细探讨四...
实验7的主题是多态性的实现,具体关注于运算符重载。在面向对象编程中,多态性允许同一种操作作用于不同的数据类型或者不同对象上,产生不同的效果。在这个实验中,我们将通过C#语言来实践这个概念,特别是在日期类...
在C#中,运算符重载使得我们可以扩展基本的运算符,如`+`、`-`、`*`、`/`等,以适应自定义数据类型的逻辑。这样做可以提高代码的可读性和表达力。 首先,让我们回顾一下运算符重载的基本概念。运算符是编程语言中...
这个问题在 C# 中改善了不少,因为运算符重载一定要写在类内,而且 intellisense 很强大。不过另一个问题又产生了…… 先来看 C++ 中的“==”重载: struct A{ int x; int y; }; inline bool operator == (const ...
#### 二十二、C#运算符重载 - **实现运算符重载**:自定义运算符的行为。 - **可重载与不可重载运算符**:哪些运算符可以被重载,哪些不可以。 #### 二十三、C#接口 - **定义接口**:接口的声明与实现。 - **接口...
在C#中,运算符重载是通过定义特殊的成员函数(通常是方法)来实现的。这个主题主要涵盖以下几个方面: 1. **什么是运算符重载**: 运算符重载并不创建新的运算符,而是为已有的运算符提供不同的操作。例如,在C#...