是什么让 Scala 的运算符重载“好”,而 C + + 的“坏”呢?

很多人认为 C + + 中的运算符重载是一件坏事(tm) ,这个错误在新语言中是不能重复的。当然,这是在设计 Java 时特别去掉的一个特性。

现在我已经开始研究 Scala 了,我发现它看起来非常像运算符重载(尽管从技术上来说它没有运算符重载,因为它没有操作符,只有函数)。然而,它似乎与 C + + 中的运算符重载在性质上没有什么不同,我记得在 C + + 中,操作符被定义为特殊的函数。

所以我的问题是,是什么让在 Scala 中定义“ +”的想法比在 C + + 中更好呢?

19022 次浏览

在 c + + 中,运算符重载从未被普遍认为是一个坏主意——只是滥用运算符重载被认为是一个坏主意。实际上并不需要语言中的运算符重载,因为它们可以通过更冗长的函数调用来模拟。在 Java 中避免运算符重载使得 Java 的实现和规范变得更加简单,这迫使程序员不能滥用操作符。关于引入运算符重载的问题,Java 社区一直存在一些争议。

Scala 的运算符重载和 C + + 的优缺点是一样的——如果你恰当地使用运算符重载,你可以编写更自然的代码——如果你不使用的话,你可以编写更加隐晦、模糊的代码。

仅供参考: 运算符在 C + + 中没有被定义为特殊的函数,它们的行为和其他函数一样——尽管在名称查找方面有一些差异,它们是否需要成员函数,以及它们可以通过两种方式调用的事实: 1)运算符语法,2)运算符-函数-id 语法。

C + + 中的运算符重载是 被许多人认为是坏人 东西(商标)

只有无知的人。在像 C + + 这样的语言中,它是绝对必需的,而且值得注意的是,其他一开始采取“纯粹主义”观点的语言,一旦他们的设计者发现它是多么的必要,就已经添加了它。

运算符重载不是一个 c + + 发明——它来自于 Algol 的 IIRC,甚至高斯林也不认为这是一个糟糕的主意。

然而,它似乎与 C + + 中的运算符重载在性质上没有什么不同,我记得在 C + + 中,操作符被定义为特殊的函数。

与“正常”成员函数相比,操作符函数没有什么特别之处。当然,您只有一组特定的运算符可以重载,但这并不意味着它们非常特殊。

正如其他答案所指出的那样,运算符重载本身并不一定是坏事。什么是坏的,当它使用的方式,使得结果代码不明显。一般来说,当你使用它们的时候,你需要让它们做一些最不令人惊讶的事情(使用操作符 + 做除法会给理性类的使用带来麻烦) ,或者正如 Scott Meyers 所说:

客户已经知道类型 所以你应该努力 让你的同类也有同样的行为 只要合理.. 怀疑,像整型人一样做。 (摘自有效的 C + + 第3版第18项)

现在有些人已经把运算符重载发挥到了极致,比如 ABc0。在这个级别上,您不知道它是如何实现的,但是它提供了一种有趣的语法来完成您想要完成的任务。我不知道这是好事还是坏事。看起来不错,但我还没用过。

C + + 从 C 继承了真正的蓝色运算符,这意味着6 + 4中的“ +”非常特殊。例如,您不能获得指向那个 + 函数的指针。

另一方面,Scala 没有那样的操作符。它只是在定义方法名称方面有很大的灵活性,而且对于非单词符号有一些内置的优先级。所以严格来说 Scala 没有运算符重载。

不管你怎么称呼它,运算符重载本身并不坏,即使在 C + + 中也是如此。问题是糟糕的程序员会滥用它。但是坦率地说,我认为剥夺程序员滥用运算符重载的能力并不意味着修复所有程序员可以滥用的东西。真正的答案是指导。http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

尽管如此,C + + 的运算符重载和 Scala 灵活的方法命名还是有区别的,恕我直言,这使得 Scala 不仅不那么容易被滥用,而且更容易被滥用。

在 C + + 中,获得 In-fix 表示法的唯一方法是使用运算符。否则,必须使用 object.message (参数)或指针 > message (参数)或函数(argument1,argument2)。因此,如果您想要在代码中使用某种 DSL 风格,那么使用操作符就有压力。

在 Scala 中,你可以通过任何消息发送获得中缀符号。“对象消息参数”完全可以,这意味着不需要使用非单词符号来获得中缀符号。

C + + 运算符重载基本上仅限于 c 运算符。再加上中间只能使用操作符的限制,这给人们施加了压力,要求他们将大量不相关的概念映射到相对较少的符号上,比如“ +”和“ >”

Scala 允许大量有效的非文字符号作为方法名。例如,我有一个嵌入式 Prolog-ish DSL,您可以在其中编写代码

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent


mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female


mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

的:-,!,?和 & 符号定义为普通方法。在 C + + 中仅 & 将是有效的,因此尝试将这个 DSL 映射到 C + + 将需要一些符号,这些符号已经引起了非常不同的概念。

当然,这也给 Scala 带来了另一种滥用。在 Scala 中你可以命名一个方法 $!如果你想的话。

对于其他语言,比如 Scala,在使用非单词函数和方法名方面是灵活的,参见 Smalltalk,在这里,每个“操作符”都只是另一个方法,而 Haskell 允许程序员定义灵活命名的函数的优先级和固定性。

本文“ C + + 与 Java 的积极遗产”直接回答了您的问题。

”C + + 同时具有堆栈分配和堆分配,您必须重载您的运算符来处理所有情况,而不会导致内存泄漏。确实很难。然而,Java 只有一个存储分配机制和一个垃圾收集器,这使得运算符重载变得微不足道。”。

Java 错误地(按照作者的说法)省略了运算符重载,因为它在 c + + 中很复杂,但是忘记了原因(或者没有意识到它不适用于 Java)。

幸运的是,像 Scala 这样的高级语言给开发人员提供了选择,同时仍然运行在相同的 JVM 上。

总的来说,这不是一件坏事。
像 C # 这样的新语言也有运算符重载。

滥用运算符重载是件坏事。

但是 C + + 中定义的运算符重载也存在问题。因为重载操作符只是方法调用的语法糖,它们的行为就像方法一样。另一方面,普通的内置运算符的行为与方法不同。这些矛盾可能会导致问题。

我头顶上的操作员 ||&&
它们的内置版本是快捷运算符。这对于超载的版本来说是不正确的,并且引起了一些问题。

事实上 +-*/全部返回它们操作的相同类型(在运算符升级之后)
重载版本可以返回任何东西(这就是滥用的地方,如果你的操作员开始返回一些仲裁者类型,用户不希望事情变糟)。

我从来没有看到过一篇文章声称 C + + 的运算符重载很糟糕。

用户可定义的操作符为语言用户提供了更高级别的表达能力和可用性。

我相信每个答案都漏掉了这个。在 C + + 中,你可以随心所欲地重载运算符,但是你不能影响计算它们的优先级。Scala 没有这个问题,IIRC。

至于说它是个坏主意,除了优先级问题,人们对于操作符的含义真的很愚蠢,而且它很少有助于提高可读性。Scala 库在这方面尤其糟糕,你必须每次都记住那些愚蠢的符号,库维护人员把头埋在沙子里说,“你只需要学习一次”。很好,现在我需要学习一些“聪明的”作者的神秘语法 * 我想要使用的库的数量。如果存在一个“总是提供操作符的文字版本”的约定,情况就不会那么糟糕了。

运算符重载并不是你真正“需要”的东西,但是当你使用 Java 的时候,如果你真的需要它,它会让你想把你的手指甲拔出来,这样你就有借口停止打字了。

你刚才发现的那个代码溢出了很长时间?是的,你需要重新输入所有的信息,这样才能和 BigInteger 一起工作。没有什么比仅仅为了改变变量的类型而不得不重新发明轮子更令人沮丧的了。

在 C + + 中唯一已知的错误是缺乏将[] = 作为单独运算符重载的能力。这可能很难在 C + + 编译器中实现,原因可能不是很明显,但是非常值得。

运算符重载没什么不好。事实上,没有为数字类型提供运算符重载是有问题的。(看看一些使用 BigInteger 和 BigDecimal 的 Java 代码。)

不过,C + + 有滥用该特性的传统。一个经常被引用的例子是位移运算符被重载以执行 I/O。

Guy Steele 在他的主题演讲“成长一门语言”中提出,运算符重载也应该使用 Java 语言。你会想知道他在前几页说了些什么,但是如果你继续读下去,你就会明白他的意思,并且获得启迪。事实上,他能做这样的演讲也是令人惊讶的。

与此同时,这个演讲启发了许多基础研究,可能包括 Scala ——这是每个人在这个领域工作时都应该阅读的论文之一。

回到重点,他的例子主要是关于数值类的(比如 BigInteger,还有一些更奇怪的东西) ,但这并不重要。

但是,如果你试图阅读代码而不去研究它所使用的库,那么滥用运算符重载会导致糟糕的结果,甚至正确的使用也会使问题复杂化,这是事实。但这是个好主意吗?OTOH,难道这样的库不应该尝试为它们的操作员包含一个操作员备忘单吗?