整数求和蓝调,short += short问题

75

使用C#编程:

short a, b;
a = 10;
b = 10;
a = a + b; // Error : Cannot implicitly convert type 'int' to 'short'.

// we can also write this code by using Arithmetic Assignment Operator as given below

a += b; // But this is running successfully, why?

Console.Write(a);

5
第一个失败是因为在规范中定义了short + short = int,就像byte + byte = int一样。不过我本来也预计第二个会失败,所以期待着看到这里的推理过程。 - Øyvind Bråthen
你找到任何原因了吗? - Mohammad Jahangeer Ansari
这个问题已经由Eric Lippert详细回答了,所以我不太确定你在问什么。简而言之,这是因为a += ba = (short)(a+b)是相同的。 - Øyvind Bråthen
1
可能是byte + byte = int... why?的重复问题。 - Mixxiphoid
5个回答

79

这里有两个问题。第一个问题是“为什么short加short的结果是int类型?”

假设short加short的结果还是short类型,看看会发生什么:

short[] prices = { 10000, 15000, 11000 };
short average = (prices[0] + prices[1] + prices[2]) / 3;

当使用short类型进行计算时,平均值显然是-9845。总和大于最大的short值,因此它会变成负数,然后你将得到一个负数。
在整数算术环境下,使用int类型进行所有计算要更加明智,因为该类型通常具有足够的范围,以避免溢出。
第二个问题是:
- short加short等于int - 将int分配给short是非法的 - a += b与a = a + b相同 - 因此,short += short应该是非法的 - 那么为什么这是合法的?
这个问题的前提是错误的;上面的第三行是错误的。C#规范在第7.17.2节中指出:
否则,如果所选运算符是预定义运算符,并且所选运算符的返回类型可以显式转换为x的类型,而y可以隐式转换为x的类型或者运算符是移位运算符,则该操作将被解释为x = (T)(x op y),其中T是x的类型,但只计算一次x。
编译器会代表你插入强制转换。正确的推理是:
- short加short等于int - 将int分配给short是非法的 - s1 += s2等同于s1 = (short)(s1 + s2) - 因此,这是合法的
如果它没有为你插入强制转换,那么在许多类型上使用复合赋值将是不可能的。

13
你的第一个示例不好。如果你将short替换为int,一切都会正常工作,但如果我们输入更大的数字(使总和大于2^31),它会导致整型溢出。我的问题是为什么short + short = int(以及byte)而int + int = int(不是long,例如)。 - Andrey
8
“真的有‘同等可能性’吗?”我以编写编译器为生,每天都要处理编译具有超过2^15种类型的程序。如果我将一堆不同程序集中的类型数量相加,则很容易会导致 short 整型数溢出。在编译器领域,我从未遇到过可以合理地发生 int 溢出的问题。当涉及到包含二十亿个类型的程序时,编译器在进行包含算术运算之前就已经耗尽了虚拟内存。在大多数情况下,32位溢出是不太可能发生的。 - Eric Lippert
7
@Eric: int 在许多计算上下文中使用,其中一些与内存/进程空间无关。随着计算硬件的不断增强,这些边界很快就会被定期超越。但也许更好的表达我的观点的方式是,如果编译器在执行算术运算时决定将小的类型扩展为更大的类型以避免意外溢出,它应该对具有可用更宽类型的所有类型进行扩展,以保持范围和表示准确性。选择停止于 int(而不是扩展至 long)有点出乎意料。 - LBushkin
5
@LBushkin: 好的,那么你希望我们 (1) 说 int + int 就是 long 类型吗?这等同于说 所有计算都始终在 long 类型中进行。你不想让人们在每个整数计算中都插入转换。 (2) 让C#根据架构具有不同的行为,就像C语言一样吗?(3) 自动扩展到较大的类型,就像VBScript一样;放弃C#的静态类型。(4) 还是其他什么方式?这些方法中的每一种都具有严重的性能或可移植性成本。这些成本是否值得? - Eric Lippert
4
@Eric:我曾经认为至少在x86架构上,可以仅对16位非扩展寄存器执行移动、加载和算术操作。并且你还可以访问低8位和高8位子寄存器(例如AX中的AL和AH)。显然,非x86架构上的行为可能会有实质性的不同。 - LBushkin
显示剩余11条评论

14

好的, += 运算符表示你将会使用一个short增加 a 的值,而 = 则表示你将用一个操作的结果覆盖这个值。操作 a + b 产生一个int,不知道它可以做其他的事情,而你正试图将那个int赋值给一个short。


1
那么增加值与添加值之间有什么不同? - Mohammad Jahangeer Ansari
我真的没想到这个。我以为 += 只是一种简写! - Camilo Martin
@jak 我的理解是=运算符会给变量赋一个新值(在这种情况下是错误类型的值),但是+=使用了另一种实现方式(即按我们期望的方式进行加法运算)。 - Camilo Martin
我一直认为a+=b只是a=a+b的简写,所以我真的很想在这里得到确认,以便完全信服。 - Øyvind Bråthen
@Camilo:C#中不存在“overloading the += operator”的说法。复合运算符不能被重载。 - Eric Lippert
显示剩余2条评论

8

您需要使用:

a = (short)(a + b);

关于赋值和加法赋值之间的行为差异,我想它与以下内容有关(来自msdn):
x+=y
is equivalent to
x = x + y
except that x is only evaluated once. The meaning of the + operator is
dependent on the types of x and y (addition for numeric operands, 
concatenation for string operands, and so forth).

然而,这有点模糊,也许有更深入了解的人可以发表评论。

1
@jak:你同时尝试了两组括号吗?你评论中的代码不同。 - UpTheCreek
2
在MSDN上哪里说了这个?如果+是预定义运算符,那么这是错误的,而在这种情况下,它显然是。在这种情况下,它等同于x = (T)(x + y),如规定的那样。如果您可以将页面链接发送给我,我将把它带到文档管理人员的注意中。 - Eric Lippert
2
@Eric:当然,它在这个页面上:http://msdn.microsoft.com/en-us/library/sa7629ew.aspx - UpTheCreek

6
这是因为int是最小的有定义+操作的有符号类型。任何比它小的类型都会先被提升为int类型。 += 操作符是基于+定义的,但对于不适合目标的结果有一个特殊规则处理。

3
这个答案是不正确的。对于每种数字类型,都没有定义 += 运算符。请参阅 C# 规范的第7.17.2节。 - Eric Lippert

0
这是因为+=被实现为一个重载函数(其中之一是short类型,编译器选择最具体的重载函数)。对于表达式(a + b),编译器默认在赋值之前将结果扩展为int类型。

1
重载运算符只能与用户定义的类、结构等类型一起使用。但是它是一个内置的算术运算符,例如 + 。 += 运算符不能直接进行重载,但可以通过重载 + 运算符来实现。欲了解更多详情,请访问以下链接:http://msdn.microsoft.com/en-us/library/sa7629ew(v=VS.71).aspx - Mohammad Jahangeer Ansari
1
jak 是正确的。在 C# 中,+= 运算符不是一个重载函数。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接