根据我的C ++教学经验,操作员重载是导致学生最悲伤的主题之一。甚至在stackoverflow上查看问题:例如,将+运算符设为外部或成员?如何处理对称性等,似乎很麻烦。
当我从C ++迁移到Java时,我担心我会错过这种能力,但除了像[]或()之类的运算符之外,我真的没有感觉到需要重载运算符。事实上,我觉得没有它们的程序更具可读性。
注意:我把它作为社区维基。我们来讨论一下。我想听听意见。
答案 0 :(得分:38)
重载运算符就像spice。一点可以让事情变得更好;太多可以让它变得难吃。
答案 1 :(得分:30)
每个C ++程序员应该知道的一些重载的例子,即使他们不赞成:
答案 2 :(得分:14)
抱怨重载的操作员很容易,只要他们不以惊人的方式行事,我真的不会看到这个问题。是的,那里有不好的例子(即使在stl中)。例如,请参阅auto_ptr的赋值运算符。重载某些运算符(例如&&
,||
和,
)几乎总是很糟糕。但在大多数情况下,让运营商做他们做广告的事情并没有真正的问题。
重载operator+
来做一些奇怪的事情是不好的做法,但是如果你把一个名为“Add”的方法放到你把对象序列化到磁盘的类中,那就差一点了。
答案 3 :(得分:6)
重载运算符可能是处理某些事情的极好方法,但非常容易被滥用。
重载<<
和>>
运算符可以轻松扩展C ++的流,包括新类型的流,I / O的新对象以及两者。重载->
使得智能指针几乎成为C ++指针的替代品。重载运算符可以使字符串连接运算符成为可能,并构建新的语法数字,就像int
一样。拥有它们可以在库中执行需要在其他语言中进行语言级更改的事情。
他们确实有其局限性。没有适合取幂的算子。只有一个乘法运算符,在某些情况下,有多种方法可以乘法(例如,使用3D矢量,至少有点和交叉乘积)。 &&
,||
和逗号运算符无法复制其内置功能,因为它们无法进行短路评估和序列点。
当然,他们可能会受到虐待。例如,没有语言要求,算术运算符必须像算术一样工作。我已经看到了可怕的事情,努力想出一个有人认为很直观的SQL符号。在写得不好的C ++程序中,不可能知道a = x * y;
做什么,因为它是a.operator=(x.operator*(y));
,或者a.operator=(operator*(x, y));
或者其他东西,并且可以写入运算符函数做任何事。
Bjarne Stroustrup设计C ++的目的是包括有用的功能而不管滥用的可能性,而James Gosling设计Java的意图是排除过度可滥用的功能,即使它们有些有用。我不清楚这些哲学中的任何一个是正确的还是不正确的,但它们是不同的。
Java旨在避免通常需要某些C ++功能的情况,例如运算符重载,多重继承和运行时类型推导,因此它们通常不会被遗漏。无论这是好还是坏,或者我都不知道。
就教学生而言,告诉他们不要让操作员自己超载(除非在定义条件下,例如仿函数和赋值操作符),而是指出库如何使用重载操作符。我不相信任何C ++学生能够做到正确,如果他们能够做到这一点,他们可以并且将自己学习。他们会知道这很棘手,因为你在课堂上禁止它。一些我永远不会相信任何比for
语句更复杂的东西会找出如何重载运算符,并且无论如何都会这样做,但这就是生命。
答案 4 :(得分:5)
运算符重载对于很多目的来说非常重要。如果没有重载operator()的能力,将无法创建函数。 在许多情况下,通用编程会成为一个痛苦的对接。如果我写一个数值算法,我依赖于行为相同的值类型,无论它是float,double,std :: complex还是一些自制类型。我依赖于定义的常用算术运算符等,因此我不必为内置类型编写单独的重载,而为自定义类型编写另一个重载。
智能指针依赖于能够重新设置解除引用操作符的对象,以便它们可以像指针一样运行。
运算符重载对于使c ++编程变得非常重要。 至于它很复杂,我只是没有看到它。它并不比创建自己的功能复杂,人们通常认为这很容易。如果将其命名为“multiply”,则它是一个函数,如果将其命名为“operator *”,则它是一个运算符。但是正文中的代码完全相同。
当然,运营商有时会受到虐待。并且&lt;&lt;或&gt;&gt;可能是边界可接受的,但它们是如此普遍地知道和使用,我认为这是公平的。
但是,如果你问过像C#这样的运算符重载,我很乐意没有它们。它们的实现更加笨拙,它们不适用于泛型,并且它们不能实现C ++使用的所有好的和方便的技巧。
答案 5 :(得分:4)
我认为这实际上取决于用例。有几种类型的运算符是必需的。例如,如果没有 - &gt;智能指针将毫无价值。和*运营商。
我还发现比较,相等和赋值运算符对特定类型非常有用。我在编辑环境中工作,因此我们自然会有几次表示坐标和跨度。当然,我们可以使用比较运算符完成所有操作,但
if ( point1 > point2 ) ...
只是看起来比
更好if ( point1.Compare(point2) < 0 ) ...
我觉得其他操作员的使用较少,虽然偶尔会派上用场。
答案 6 :(得分:3)
我认为运营商超载不是一个坏主意。我认为将隐式转换作为默认行为是一个坏主意。默认的隐式转换与运算符重载相结合是一个真的坏主意。
完全取消隐式转换 - 或者让它依赖于“隐含”关键字 - 语言永远不会有无数文章中讨论的潜在陷阱和问题,如this one。
答案 7 :(得分:2)
正如Neil在他的回答中指出的那样,运算符重载是学习面向对象的C ++习语的必要主题。我会谨慎地教导学生,如果你没有遵循惯用惯例实现重载操作符,它可能导致非常错误和意外的行为。运营商重载不是创造性的好时机。
答案 8 :(得分:1)
我使用它们时的操作员和案例:
operator-&gt ;, operator * - 用于代理对象和不同的包装器
operator = - 用于避免副本上的意外行为
运算符&lt; (&gt;,&lt; =,&gt; =) - 用于存储在地图或集合中(但通常最好将仿函数传递到此版本中)。
运算符&lt;&lt; (&gt;&gt;) - 用于流和boost :: lexical_cast兼容性。
operator ==,!= - 允许比较对象
操作员! - 有时改为valid()函数
operator Type - 用于转换为其他类型。
operator() - 对于smart functor,当不允许boost时。
就是这样 有时我以前使用过其他运算符,但那是我的数学函数。
还应该注意逻辑运算符(&amp;&amp;,||) - 我们将与标准语义有所区别:
ptr && ptr->method()
如果我们重载了运算符&amp;&amp;。,可能有其他意义
答案 9 :(得分:1)
我真的很喜欢在C ++中为非内置类型重载算术运算符的能力。但仅适用于具有算术行为的类型;例如,定点类,3D矢量类,复数类,任意长度的“bignum”类。我在Java中编写了类似的代码,并且因为必须编写a.Add(b)
而不是a+b
这样的内容而烦恼。请注意,我是一名训练有素的数学家;在我看来,运算符重载让你可以在C ++中获得一点特定于领域的语言,而不必实际实现。
但是当我看到例如operator+
被重载了operator<<
(遵循狡猾但完善的iostream惯例)或STL .push_back()
更好的功能时,我真的很烦恼 - 喜欢模式。
关于operator()
...发现boost::bind
&amp; boost::function
我无法想象没有算子的生活。如果不重载operator*
,operator->
等,智能指针就不会那么方便。
答案 10 :(得分:0)
重载运算符的问题在于,有些人喜欢使用与运算符的原始(C)目的无关的功能来重载它们(这里我指向&gt;&gt;和&lt; ;&lt; std :: iostream中的运算符。)。在我看来,唯一一次你应该重载运算符是当重载完全匹配底层运算符的意义时(即&lt;和&gt;必须进行比较)。或者,你必须以某种方式重载它才能进行交互与另一个图书馆。
坦率地说,除非我需要一个库,否则我不会重载操作符。它只是让读者太努力去弄清楚发生了什么。
答案 11 :(得分:0)
最好避免重载的运算符之一是conversion operator。它会导致意想不到的结果,甚至STL也不会超载它,而是更喜欢功能样式转换:
std::string str = "foo";
char *ch = str.c_str(); //rather than char *ch = str.operator *char();
答案 12 :(得分:0)
我想念Java中的运算符重载,特别是在以下情况下:
算法或仿函数的类: (战略模式,责任链,口译员等)。自然是重载op();相反,每个程序员都会为函数提供(通常是不兼容的,因此令人困惑的)名称:“eval”,“evaluate”,“operation”,“doIt”等。因此,清晰度会降低,因为这些名称我们被迫使用不要让他们的意思明显。
转换为其他类型的类: 在C ++中,它是运算符Type(),既可用于实际转换,也可用于生成所需类的内部成员。第二种情况在Java中出现很多,当一个类不必要的最终但你想要向它添加操作时:
class DecoratedStringBuffer { //extends StringBuffer doesn't work, as String is final
private String byContainmentThen;
public decorate(final String prefix, final String suffix) { ... }
public append(final String s) { byContainmentThen.append(s);}
// other forwarding functions
}
由于DecoratedStringBuffer不是一个StringBuffer,在它离开你的代码并返回到客户端代码之前,需要将其转换回来,可能是最终应用后缀和前缀的函数。如果我们可以调用该运算符StringBuffer()(如果Java,如C ++,可以应用一个用户提供的转换),那就更好了。
相反,因为没有惯例,我们必须给它一个更加含糊不清的名称。 getStringBuffer()是一个明显的名称,但对于许多Java用户来说,这意味着我们不想要的相应的setStringBuffer。即使它并不意味着,这个名称含糊不清:是StringBuffer你得到的是我们操作的那个,还是别的什么?
toStringBuffer()是一个更好的名称,我倾向于使用的模式,但是有人阅读代码会想知道为什么看起来像getter的东西被称为“to”ClassName。
老实说,除了设计数值类或“明显”可以解决cocatenatable对象,重载op +几乎没用。而且由于Java不像C ++那样基于价值,因此对于op =没有多大用处; Java并没有试图让所有东西都像原始的int值类一样。它是op()和我想念的转换运算符。
答案 13 :(得分:-1)
拥有功能但不是必不可少的功能是一件好事。在许多情况下,我发现它们比它们带来的好处更令人困惑。就像在JaredPars示例中一样,.compare函数比'&gt;'更明显。对我来说。直接看到point1是一个对象而不是一个原始数据类型。我喜欢我使用的libs中的重载操作符,但在我自己的代码中,我很少使用它们。
编辑:我选择的函数名称不一致。将.compare替换为.greaterThan使其更清晰。我的意思是,对于我而言绑定到对象的函数名称比在第一次看起来与对象没有关联的运算符更明显。 Imho一个精心选择的函数名称更容易阅读。