下面的代码(可用作控制台应用程序):
static void Main(string[] args)
{
int i = 0;
i += i++;
Console.WriteLine(i);
Console.ReadLine();
}
i的结果是0。我以为会有2个(和我的一些同事一样)。可能编译器创建了某种结构,导致i为零。
我期望2的原因是,在我的思路中,右边的语句会先求值,使I加1。然后加上i,因为i已经是1了,所以是1加1。所以1 + 1 = 2。很明显,事实并非如此。
你能解释编译器做什么或者在运行时发生了什么吗?为什么结果是零?
某种免责声明:我非常清楚您不会(而且可能不应该)使用此代码。我知道我永远不会。尽管如此,我觉得了解它为什么会以这样的方式工作以及究竟发生了什么是很有趣的。
关于这一点,我一直记得一个很好的助记法:
如果++位于表达式之后,则返回之前的值。下面的代码
int a = 1;
int b = a++;
是1,因为a在被a后面的++加起来之前是1,人们称之为后缀符号。还有一种前缀表示法,情况正好相反:如果++位于前面,表达式将返回操作之后的值:
int a = 1;
int b = ++a;
这里B是2。
对于你的代码,这意味着
int i = 0;
i += (i++);
i++返回0(如上所述),因此0 + 0 = 0。
i += (++i); // Here 'i' would become two
Scott Meyers在“有效的c++编程”中描述了这两种符号之间的区别。在内部,i++ (postfix)记住i的值,并调用前缀符号(++i)并返回旧值i。这就是为什么你应该总是在for循环中使用++i(尽管我认为所有现代编译器都在for循环中将i++转换为++i)。
你的问题唯一正确的答案是:因为它是没有定义的。
我+ = + +;结果0是未定义的。
如果你愿意的话,语言评估机制中的一个bug。甚至更糟!设计中的bug。
想要证明吗?你当然想!
int t = 0;int i = 0;t + = + +;/ / t = 0;i = 1
现在这…是直观的结果!因为我们首先对t求值并赋值只有在求值和赋值之后我们才进行后期操作,不是很合理吗?
i=i++和i=i对i产生相同的结果是合理的吗?
而t=i++和t=i对于i有不同的结果。
post操作应该发生在语句求值之后。
因此:
int i=0;
i+=i++;
应该是一样的,如果我们写
int i=0;
i = i + i ++;
因此和:
int i=0;
i= i + i;
i ++;
因此和:
int i=0;
i = i + i;
i = i + 1;
如果我们理性思考,任何不是1的结果都表明编译器中的错误或语言设计中的错误-然而MSDN和许多其他来源告诉我们“嘿,这是未定义的!”
现在,在我继续之前,即使我给出的这组例子也没有得到任何人的支持或承认。然而,这是根据直觉和理性的方式应该得到的结果。
编码器不应该知道程序集是如何编写或翻译的!
如果它以一种不尊重语言定义的方式编写——这是一个bug!
And to finish I copied this from Wikipedia, Increment and decrement operators :
Since the increment/decrement operator modifies its operand, use of such an operand more than once within the same expression can produce undefined results. For example, in expressions such as x − ++x, it is not clear in what sequence the subtraction and increment operators should be performed. Situations like this are made even worse when optimizations are applied by the compiler, which could result in the order of execution of the operations to be different than what the programmer intended.
因此。
正确的答案是不应该使用!(因为它没有定义!)
是的. .-它有不可预测的结果,即使c#编译器试图以某种方式规范化它。
我没有找到任何关于c#的文档,把你们所有人都记录下来的行为描述为一种正常的或定义良好的语言行为。但我的发现恰恰相反!
[从MSDN文档中复制后缀自增和自减操作符:++和——]
当后缀操作符应用于函数实参时,不保证实参的值在传递给函数之前是递增或递减的。有关更多信息,请参阅c++标准中的1.9.17节。
注意这些话并不能保证……
这只是从左到右、自底向上的抽象语法树的计算。从概念上讲,表达式的树是从上到下遍历的,但是当递归从树的底部弹出时,计算就展开了。
// source code
i += i++;
// abstract syntax tree
+=
/ \
i ++ (post)
\
i
计算从考虑根节点+=开始。这是这个表达式的主要组成部分。必须计算+=的左操作数,以确定存储变量的位置,并获得前值为零。接下来,右边必须求值。
右边是一个后递增的++操作符。它有一个操作数i,它既作为值的来源,也作为值的存储位置进行计算。运算符对i求值,找到0,然后将1存储到该位置。它根据返回先验值的语义返回先验值0。
现在控制回到+=运算符。现在它有了完成操作的所有信息。它知道存储结果的位置(i的存储位置)以及先前的值,并且它有要添加到先前值的值,即0。所以i最后是0。
像Java一样,c#通过固定求值顺序,清除了C语言中一个非常愚蠢的方面。从左到右,自底向上:程序员可能期望的最明显的顺序。
c#在做什么,以及困惑的“为什么”
我还期望值为1…但对这个问题的一些探索确实澄清了一些观点。
考虑以下方法:
static int SetSum(ref int a, int b) { return a += b; }
static int Inc(ref int a) { return a++; }
我期望i+ = i++与SetSum(ref I, Inc(ref I))相同。语句之后的i值是1:
int i = 0;
SetSum(ref i, Inc(ref i));
Console.WriteLine(i); // i is 1
但后来我得出了另一个结论……I += i++实际上和I = I + i++…所以我创建了另一个类似的例子,使用这些函数:
static int Sum(int a, int b) { return a + b; }
static int Set(ref int a, int b) { return a = b; }
调用这个Set(ref i, Sum(i, Inc(ref i)))后,i的值为0:
int i = 0;
Set(ref i, Sum(i, Inc(ref i)));
Console.WriteLine(i); // i is 0
这不仅解释了c#在做什么……但这也是为什么很多人对它感到困惑……包括我。