有意义的运算符重载示例


12

在学习C#时,我发现C#支持运算符重载。我有一个很好的例子有问题:

  1. 有道理(例如,添加名为羊和牛的类)
  2. 不是两个字符串串联的示例

欢迎使用基类库中的示例。


10
请定义“感觉”!认真地说,就这一点进行的激烈而激烈的辩论表明,就这一点存在巨大分歧。许多机构拒绝超载的操作员,因为他们可以做完全出乎意料的事情。其他人回答说,同样可以选择完全不直观的方法名,但这并不是拒绝命名代码块的理由!您几乎肯定不会得到任何通常被认为明智的示例。对来说似乎明智的示例-也许吧。
Kilian Foth

完全同意@KilianFoth。最终,编译的程序对编译器确实有意义。但是,如果重载==做乘法,对我来说是有意义的,但对其他人却可能没有意义!这是关于哪种设施编程语言是否合法的问题,还是我们在谈论“编码最佳实践”?
Dipan Mehta

Answers:


27

适当的运算符重载的明显示例是任何行为与数字运算相同的类。因此,BigInt类(如Jalayn所建议),复数矩阵类(如Superbest所建议)都具有相同的运算,因此普通数可以很好地映射到数学运算符上,而时间运算(如svick所建议)很好地映射到子集上这些操作。

稍微抽象一点,在执行类似集合的操作时可以使用运算符,因此operator+可以是并operator-可以是补数等。但是,这的确开始扩展了范式,特别是如果您对加法运算符使用加法或乘法运算符t是可交换的,正如您可能期望的那样。

C#本身就是非数值运算符重载的一个很好的例子。它使用+=-=来增加和减少代表,即注册和注销他们。这之所以行之有效,是因为+=and -=运算符可以按您期望的那样工作,从而使代码更加简洁。

对于纯粹主义者来说,字符串+运算符的问题之一是它不是可交换的。"a"+"b"与相同"b"+"a"。我们理解字符串的这种异常,因为它非常常见,但是如何判断operator+对其他类型的使用是否是可交换的呢?除非对象是字符串状的,否则大多数人都会假设它是,但是您永远不会真正知道人们会假设什么。

与字符串一样,矩阵的寓言也是众所周知的。显然,这Matrix operator* (double, Matrix)是一个标量乘法,而例如Matrix operator* (Matrix, Matrix)是一个矩阵乘法(即点积乘法矩阵)。

类似地,将运算符与委托一起使用显然与数学相距甚远,因此您不太可能犯这些错误。

顺便提一句,在2011年ACCU会议上罗杰·奥尔Roger Orr)史蒂夫·洛夫Steve Love)提出了关于“ 某些事物比其他事物更平等”的会议-考察平等,价值和认同的许多含义。他们的幻灯片可以下载,理查德·哈里斯(Richard Harris)关于浮点相等附录也可以下载。摘要:要非常小心使用operator==,这里是龙!

运算符重载是一种非常强大的语义技术,但易于过度使用。理想情况下,仅应在上下文非常清楚重载运算符的影响是什么的情况下使用它。在许多方面a.union(b)比更清晰a+b,而且a*b比比较模糊a.cartesianProduct(b),尤其是笛卡儿积的结果将是一个SetLike<Tuple<T,T>>,而不是一个SetLike<T>

运算符重载的真正问题出在程序员认为某个类将以一种方式运行,而实际上却以另一种方式运行时。我建议这种语义冲突是要避免的重要。


1
您说矩阵上的运算符映射得很好,但是矩阵乘法也不是可交换的。代表的运算符也更加强大。您可以d1 + d2为同一类型的任何两个委托执行操作。
svick '02

1
@Mark:“点积”仅在向量上定义;将两个矩阵相乘简称为“矩阵相乘”。区别不只是语义上的:点积返回标量,而矩阵乘法返回矩阵(顺便说一下,是非可交换的)
BlueRaja-Danny Pflughoeft 2012年

26

我很惊讶,没有人提到BCL中最有趣的情况之一:DateTimeTimeSpan。您可以:

  • 加或减两个TimeSpans以获得另一个TimeSpan
  • 在上使用一元减号TimeSpan来求反TimeSpan
  • 减去两个DateTimes得到一个TimeSpan
  • TimeSpan从a 加上或减去DateTime得到另一个DateTime

另一组运营,可以使在很多类型的感觉是<><=>=。例如,在BCL中Version实现它们。


非常真实的例子,而不是古怪的理论!
SIslam

7

我想到的第一个示例是BigInteger的实现,该实现使您可以处理大符号整数。查看MSDN链接以查看有多少运算符已过载(也就是说,有一个很大的列表,我没有检查是否所有运算符都已过载,但是看起来确实如此)

另外,由于我也使用Java并且Java不允许重载运算符,因此编写起来非常好

BigInteger bi = new BigInteger(0);
bi += 10;

比起Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

我很高兴看到这一点,因为我一直在与Irony混蛋,并且它大量使用了运算符重载。这是它可以做什么的一个示例

因此,讽刺的是“ .NET语言实现工具包”,并且是解析器生成器(生成LALR解析器)。无需像yacc / lex之类的解析器生成器那样学习新的语法/语言,您可以使用带有操作符重载的C#编写语法。这是一个简单的BNF语法

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

因此,这是一个简单的小语法(如果有不一致之处,请原谅,因为我只是在学习BNF并构建语法)。现在让我们看一下C#:

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

如您所见,随着运算符的重载,用C#编写语法几乎完全是用BNF编写语法。对我来说,这不仅有意义,而且是运算符重载的一个很好的使用。


3

关键示例是operator == / operator!=。

如果要通过数据值而不是通过引用轻松比较两个对象,则需要重载.Equals(和.GetHashCode!),并且可能还希望使用!=和==运算符以保持一致性。

不过,我从未在C#中看到其他运算符的任何重载(我想虽然在某些极端情况下它可能有用)。


1

MSDN中的此示例显示了如何实现复数并使它们使用normal +运算符。

另一个示例显示了如何进行矩阵加法,还说明了如何不使用它来将汽车添加到车库(请阅读链接)。


0

很好地使用重载可能很少见,但确实会发生。

重载operator ==和operator!=表现出两种思路:那些说使事情变得容易的人,以及反对说它使比较地址的人(即,我指向内存中的确切位置,而不仅仅是相同位置的副本)宾语)。

我发现强制转换运算符重载在特定情况下很方便。例如,我必须在XML中序列化/反序列化一个表示为0或1的布尔值。正确的(隐式或显式,我忘记了)强制转换运算符从boolean到int然后返回就可以了。


4
它不会阻止比较地址:您仍然可以使用object.ReferenceEquals()
dan04 '02

@ dan04很高兴知道!
MPelletier

比较地址的另一种方法是通过强制转换来使用对象==(object)foo == (object)bar始终比较引用。但是我更喜欢ReferenceEquals()@ dan04提到的,因为它的作用更加清晰。
svick

0

当人们重载运算符时,它们并不是人们通常会想到的事情,但是我认为能够重载的最重要的运算符之一是转换运算符

转换运算符对于可以“反糖化”为数值类型或在某些情况下可以充当数值类型的值类型特别有用。例如,您可以定义一个Id表示特定标识符的特殊类型,并且可以提供的隐式转换,int以便可以将传递Id给采用的方法int,而将显式转换为intId因此任何人都不能将传递int给。一种方法,Id无需先进行转换。

作为C#以外的示例,Python语言包含许多作为可重载运算符实现的特殊行为。其中包括in用于成员资格测试的()运算符,用于将对象当作函数调用的len运算符以及用于确定对象的长度或大小的运算符。

然后,您将拥有诸如Haskell,Scala之类的语言以及许多其他功能性语言,其中诸如此类+的名称只是普通函数,而根本不是运算符(并且语言支持在infix位置使用函数)。


0

Point结构System.Drawing中命名空间使用重载比较使用操作符重载两个不同的位置。

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

如您所见,使用重载比较两个位置的X和Y坐标要容易得多。


0

如果您熟悉数学向量,则可能会发现在重载+运算符方面的用途。你可以添加一个矢量a=[1,3]b=[2,-1]和获得c=[3,2]

重载等于(==)也是有用的(即使实现一个equals()方法可能更好)。继续执行矢量示例:

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

想象一下在表单上绘图的一段代码

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

另一个常见的示例是使用结构以矢量形式保存位置信息时。

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

只能在以后用作

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
您添加的载体,而不是立场:\这是当一个很好的例子operator+应该超载(可以实现在矢量方面的一个点,但你不应该能够补充两点)
BlueRaja -丹尼Pflughoeft

@ BlueRaja-DannyPflughoeft:添加位置以产生另一个位置没有意义,但是减去它们(以产生向量)和平均它们一样。可以通过来计算p1,p2,p3和p4的平均值p1+((p2-p1)+(p3-p1)+(p4-p1))/4,但这似乎有些尴尬。
supercat 2014年

1
在仿射几何中,您可以使用点和线进行代数运算,例如加法,缩放等。尽管实现需要均质坐标,但无论如何通常都在3D图形中使用。两点相加实际上就是它们的平均值。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.