在学习C#时,我发现C#支持运算符重载。我有一个很好的例子有问题:
- 有道理(例如,添加名为羊和牛的类)
- 不是两个字符串串联的示例
欢迎使用基类库中的示例。
==
做乘法,对我来说是有意义的,但对其他人却可能没有意义!这是关于哪种设施编程语言是否合法的问题,还是我们在谈论“编码最佳实践”?
在学习C#时,我发现C#支持运算符重载。我有一个很好的例子有问题:
欢迎使用基类库中的示例。
==
做乘法,对我来说是有意义的,但对其他人却可能没有意义!这是关于哪种设施编程语言是否合法的问题,还是我们在谈论“编码最佳实践”?
Answers:
适当的运算符重载的明显示例是任何行为与数字运算相同的类。因此,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>
。
运算符重载的真正问题出在程序员认为某个类将以一种方式运行,而实际上却以另一种方式运行时。我建议这种语义冲突是要避免的重要。
d1 + d2
为同一类型的任何两个委托执行操作。
我想到的第一个示例是BigInteger的实现,该实现使您可以处理大符号整数。查看MSDN链接以查看有多少运算符已过载(也就是说,有一个很大的列表,我没有检查是否所有运算符都已过载,但是看起来确实如此)
另外,由于我也使用Java并且Java不允许重载运算符,因此编写起来非常好
BigInteger bi = new BigInteger(0);
bi += 10;
比起Java:
BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));
我很高兴看到这一点,因为我一直在与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编写语法。对我来说,这不仅有意义,而且是运算符重载的一个很好的使用。
很好地使用重载可能很少见,但确实会发生。
重载operator ==和operator!=表现出两种思路:那些说使事情变得容易的人,以及反对说它使比较地址的人(即,我指向内存中的确切位置,而不仅仅是相同位置的副本)宾语)。
我发现强制转换运算符重载在特定情况下很方便。例如,我必须在XML中序列化/反序列化一个表示为0或1的布尔值。正确的(隐式或显式,我忘记了)强制转换运算符从boolean到int然后返回就可以了。
object.ReferenceEquals()
。
==
:(object)foo == (object)bar
始终比较引用。但是我更喜欢ReferenceEquals()
@ dan04提到的,因为它的作用更加清晰。
当人们重载运算符时,它们并不是人们通常会想到的事情,但是我认为能够重载的最重要的运算符之一是转换运算符。
转换运算符对于可以“反糖化”为数值类型或在某些情况下可以充当数值类型的值类型特别有用。例如,您可以定义一个Id
表示特定标识符的特殊类型,并且可以提供的隐式转换,int
以便可以将传递Id
给采用的方法int
,而将显式转换为int
,Id
因此任何人都不能将传递int
给。一种方法,Id
无需先进行转换。
作为C#以外的示例,Python语言包含许多作为可重载运算符实现的特殊行为。其中包括in
用于成员资格测试的()
运算符,用于将对象当作函数调用的len
运算符以及用于确定对象的长度或大小的运算符。
然后,您将拥有诸如Haskell,Scala之类的语言以及许多其他功能性语言,其中诸如此类+
的名称只是普通函数,而根本不是运算符(并且语言支持在infix位置使用函数)。
该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坐标要容易得多。
想象一下在表单上绘图的一段代码
{
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;
}
operator+
应该不超载(可以实现在矢量方面的一个点,但你不应该能够补充两点)
p1+((p2-p1)+(p3-p1)+(p4-p1))/4
,但这似乎有些尴尬。