为什么减号“-”通常不会以与加号相同的方式重载?


64

加号+用于加法和字符串连接,但它的伴随符号:减号-通常看不到减号或除减法以外的其他情况。这可能是什么原因或局限性?

考虑下面的JavaScript示例:

var a = "abcdefg";
var b = "efg";

a-b == NaN
// but
a+b == "abcdefgefg"

35
应该删除哪个“ yy”?
哥哈赫2015年

12
如果我采用“ +”号的行为,那么最正确的做法是合理的。
Digvijay Yadav 2015年

46
二进制+运算符被两个完全不相关的含义“数字加法”和“字符串串联”重载是非常糟糕的。值得庆幸的是,有些语言提供一个单独的连接运算符,如.(Perl5中,PHP),~(Perl6),&(VB), ++(哈斯克尔),...
阿蒙

6
@MasonWheeler他们使用->(考虑取消C语言中的成员访问,因为虚拟方法调用必然涉及类似指针的间接访问)。.尽管这是一种越来越普遍的约定,但是没有语言设计法则要求方法调用/成员访问权限才能使用运算符。您知道Smalltalk没有方法调用运算符吗?简单的并置object method就足够了。
阿蒙(Amon)

20
Python 减去负值,以进行集减(也可以在用户定义的类型中重载)。Python集还使交集/联合/等的大多数按位运算符重载。
凯文

Answers:


116

简而言之,对于人们想要编写算法的字符串,没有任何特别有用的类似减法的运算。

+操作者一般表示一种添加剂的操作独异,即,与身份元素的相联操作:

  • A +(B + C)=(A + B)+ C
  • A + 0 = 0 + A = A

将此操作符用于整数加法,字符串连接和集合并集之类的事情是有意义的,因为它们都具有相同的代数结构:

1 + (2 + 3) == (1 + 2) + 3
1 + 0 == 0 + 1 == 1

"a" + ("b" + "c") == ("a" + "b") + "c"
"a" + "" == "" + "a" == "a"

我们可以使用它来编写方便的算法,例如concat可以对任何“可连接的”事物进行处理的函数,例如:

def concat(sequence):
    return sequence.reduce(+, 0)

-涉及减法时,通常会讨论group的结构,该结构为每个元素A 添加一个反数 -A,从而:

  • A + -A = -A + A = 0

尽管这对于整数和浮点减法,甚至设置差异之类的东西有意义,但对于字符串和列表却没有太大意义。的逆是"foo"什么?

有一个称为取消单面体的结构,它没有逆,但具有取消属性,因此:

  • A − A = 0
  • A-0 = A
  • (A + B)− B = A

这是您描述的结构,其中"ab" - "b" == "a",但"ab" - "c"未定义。只是我们没有许多使用这种结构的有用算法。我想如果您将串联视为串行化,那么可以将减法用于某种形式的解析。


2
对于集合(和多集合),减法是有意义的,因为与序列不同,元素的顺序无关紧要。
CodesInChaos

@CodesInChaos:我添加了对它们的提及,但我不太愿意将集合作为一个组的示例-我不相信它们构成一个集合,因为您通常无法构造集合的逆。
乔恩·普迪

12
实际上,该+操作对于数字也是可交换的,即A+B == B+A,这使其成为字符串连接的不佳选择。这,加上令人困惑的运算符优先级,导致使用+字符串连接来处理历史错误。但是,的确,-对任何字符串操作使用都会使情况变得更糟……
Holger 2015年

2
@Darkhogg:对!PHP是.从Perl 借来的;它~在Perl6中,可能在其他地方。
乔恩·普迪

1
@MartinBeckett,但您可以看到该行为可能与.text.gz.text…… 混淆
蜘蛛侠鲍里斯(Boris the Spider)

38

因为任何两个有效字符串的连接始终是有效操作,但是相反的情况并非如此。

var a = "Hello";
var b = "World";

这里应该a - b是什么?实际上,没有很好的方法来回答该问题,因为该问题本身是无效的。


31
@DigvijayYadav,如果您从5个苹果中除去5个芒果,那么必须有一个-5个芒果的计数器吗?它什么都不做?您能否定义得足够好,使其可以被广泛接受并放入所有语言的编译器和解释器中,以便以这种形式使用此运算符?这是最大的挑战。
JB金王

28
@DigvijayYadav:因此,您刚刚描述了实现此目标的两种可能方法,并且有一个很好的论据认为每种方法都有效,因此我们已经搞乱了指定此操作的想法。:P
梅森·惠勒

13
@smci在我看来5 + False显然应该是一个错误,因为数字不是布尔值,而布尔值不是数字。
梅森惠勒2015年

6
@JanDvorak:没有什么特别的“ Haskelly”;这是基本的强类型。
梅森惠勒2015年

5
@DigvijayYadav所以(a+b)-b = a(希望如此!),但(a-b)+b有时是a,有时a+b取决于是否b是的子字符串a?这是什么疯狂?

28

因为-用于字符串操作的运算符没有足够的“语义内聚”。仅当绝对清楚其操作数的重载是什么,并且字符串减法不符合该限制时,才应重载运算符。

因此,首选方法调用:

public string Remove(string source, string toRemove)
public string Replace(string source, string oldValue, string newValue)

在C#语言中,我们使用+字符串连接,因为

var result = string1 + string2 + string3;

代替

var result = string.Concat(string1, string2, string3);

从语义的角度来看,即使函数调用可能更“正确”,它还是很方便且可以说更易于阅读的。

+在这种情况下,操作员实际上只能指一件事。这不是正确的-,因为减去字符串的概念是模棱两可的(Replace(source, oldValue, newValue)使用参数""作为newValue参数的函数调用消除了所有疑问,并且该函数可用于更改子字符串,而不仅仅是删除它们)。

当然,问题在于运算符的重载取决于传递给运算符的类型,并且如果在应该传递数字的地方传递字符串,则可能会得到意想不到的结果。另外,对于许多串联(即循环),StringBuilder对象是更可取的,因为每次使用+都会创建一个全新的字符串,并且性能可能会受到影响。因此,+运算符甚至都不适合所有情况。

运算符重载比+字符串串联运算符具有更好的语义凝聚力。这是一个添加两个复数的数字:

public static Complex operator +(Complex c1, Complex c2) 
{
    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}

8
+1给定两个字符串A和B,我可以将AB视为“从A的末尾删除结尾的B”,“从A的某个位置删除B的实例”,“从A的某个位置删除B的所有实例” ”,甚至“从A中删除在B中找到的所有字符”。
Cort Ammon 2015年

8

Groovy语言确实允许-

println('ABC'-'B')

返回:

AC

和:

println( 'Hello' - 'World' )

返回:

Hello

和:

println('ABABABABAB' - 'B')

返回:

AABABABAB

11
有趣-因此它选择删除第一个匹配项?一个完全违反直觉的行为的好例子。
绿巨人

9
因此,我们('ABABABABA' + 'B') - 'B'与起始值几乎没有什么不同'ABABABABA'
CVn 2015年

3
@MichaelKjörlingOTOH,(A + B) - A == B对于每个A和B。我可以称之为左减法吗?
约翰·德沃夏克

2
Haskell具有++串联功能。它适用于任何列表,字符串只是字符列表。它还具有\\,从左参数中删除右参数中每个元素的第一次出现。
约翰·德沃夏克

3
我觉得这些示例正是为什么字符串不应该包含减号运算符的原因。这是不一致且不直观的行为。当我想到“-”时,我肯定不会想到,“如果出现匹配的字符串,请删除它的第一个实例,否则就什么也不做。”
enderland

6

在更多情况下,加号在上下文上可能更有意义,但是set对象中的反例(也许是证明规则的异常)是set对象,它提供-但不提供+

>>> set('abc') - set('bcd')
set(['a'])
>>> set('abc') + set('bcd')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'set' and 'set'

使用+符号没有意义,因为意图可能是不明确的-这意味着设置相交或并集吗?相反,它|用于联合和&交集:

>>> set('abc') | set('bcd')
set(['a', 'c', 'b', 'd'])
>>> set('abc') & set('bcd')
set(['c', 'b'])

2
这更有可能是因为在数学中定义了集减法,但没有定义集加法。
Mehrdad 2015年

使用“-”似乎是狡猾的;真正需要的是“但不是”运算符,该运算符在对整数执行按位运算时也将很有用。如果30〜&7为24,则将〜&与set配合使用将与&和|非常适合。即使集合缺少〜运算符。
2015年

1
set('abc') ^ set('bcd')set(['a', 'd'])如果询问对称差,则返回。
亚伦·霍尔

3

-”在一些复合词被使用(例如,“现场”)用于接合不同部分到同一字。为什么不使用“ -”将编程语言中的不同字符串连接在一起?我认为这完全有道理!为了这个见鬼+的废话!

但是,让我们尝试从更抽象的角度来看待这个问题。

您将如何定义字符串代数?您将进行哪些操作,以及对他们适用什么法律?他们的关系是什么?

请记住,可能绝对没有歧义!每个可能的情况都必须得到很好的定义,即使这确实意味着不可能做到这一点!代数越小,越容易完成。

例如,加或减两个字符串实际上意味着什么?

如果添加两个字符串(例如let a = "aa"b = "bb"),将得到aabb结果a + b吗?

怎么b + a样 那会是bbaa吗?为什么不aabb呢?如果从加法aa结果中减去会怎样?您的字符串中会有负数的概念aa吗?

现在返回到此答案的开头,并替换spaceshuttle而不是字符串。概括地说,为什么要为任何类型定义或不定义任何操作?

我要说的是,没有什么可以阻止您为任何事物创建代数。可能很难找到有意义的操作,甚至很难找到有用的操作。

对于字符串,串联几乎是我遇到的唯一明智的方法。没关系,用什么符号表示操作。


1
“对于字符串,串联几乎是我遇到过的唯一明智的选择”。那你不同意Python 'xy' * 3 == 'xyxyxy'吗?
smci 2015年

3
当然,@ smci只是乘法-重复-加法
jonrsharpe 2015年

连接航天飞机的正确运算符是什么?
Mindor先生,2015年

4
@ Mr.Mindor backspace ...删除航天飞机之间的空间。
YoungJohn
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.