为什么这个表达式i + = i ++与Java和C不同?
我知道前缀和后缀操作…… ++ i和i ++之间的区别等等。
但我想我在这里遗漏了一些东西。 您可以在下面找到代码:
package test; public class Test { public static void main (String[] args) { int i=0; i+=i++; System.out.println(i); // Prints 0 i = i + (i++); System.out.println(i); // Prints 0 i = i + (i+1); System.out.println(i); // Prints 1 } }
所以输出是:
0 0 1
我在C中尝试了相同的代码:
#include #include main() { int i=0; i+=i++; printf("%d", i); // prints 1 i = i + (i++); printf("%d", i); // prints 3 i = i + (i+1); printf("%d", i); // prints 7 }
输出是:
1 3 7
为什么i+=i++
不会增加i
而C中的相同代码会增加值?
Java的
在Java中,表达式具有明确定义的含义。 复合赋值运算符的规范说:
formsE1 op = E2的复合赋值表达式等效于E1 =(T)((E1)op(E2)) ,其中T是E1的类型,除了E1仅被评估一次。
…
在运行时,表达式以两种方式之一进行评估。 如果左侧操作数表达式不是数组访问表达式,则需要四个步骤:
- 首先,评估左侧操作数以产生变量。 如果此评估突然完成,则赋值表达式出于同样的原因突然完成; 不评估右侧操作数,也不进行赋值。
- 否则,保存左侧操作数的值,然后评估右侧操作数。 如果此评估突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何分配。
- 否则,左侧变量的保存值和右侧操作数的值用于执行复合赋值运算符指示的二进制运算。 如果此操作突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何赋值。
- 否则,二进制操作的结果将转换为左侧变量的类型,经过值集转换(第5.113节)到相应的标准值集(不是扩展指数值集),结果转换的内容存储在变量中。
所以
i += i++;
相当于
i = i + i++;
因为左侧的值在开始时被保存,然后被添加到右侧的值,并且因为Java中的表达式评估是从左到右,所以由i++
引起的i++
的修改被覆盖了后续任务。
这一切都意味着
i += i++;
相当于
i += i;
C
在C表达式中
i += i++;
有未定义的行为。 因此,执行此代码时可能会发生任何事情。 这意味着您无法预测语句完成后i
将拥有的价值。
因此,完全可以预期C程序的输出与Java程序的输出不同。
为什么这个表达式i + = i ++与Java和C不同?
i+=i++
与i = i + i++
在Java(和C#)中, 需要从左到右计算它。 那是:
left = i; // left of + right = i; // right of +, original value incr = right + 1; // incremented value i = incr; // effect of ++ sum = left + right; // sum of left and right sides of + i = sum; // assign sum to i
在C中, 可以但不要求从左到右计算。 特别是作业
left = i;
和
incr = right + 1; i = incr;
可以按其他顺序完成。 也就是说,这个顺序在C中是合法的:
right = i; // right of +, original value incr = right + 1; // incremented value i = incr; // effect of ++ left = i; // left of + sum = left + right; // sum of left and right sides of + i = sum;
这显然会产生不同的结果。 (这不是唯一可能的重新排序,但它是合理的。)
因此,C和Java在这里是不同的,因为程序片段在C中没有很好地定义 。 在Java中它是明确定义的,它只是糟糕的风格。
i+=i++
与i = i + i++
。 让i
在你的例子中为零。
现在评估如下:
i = 0 + i++ //evaluate i++ to zero, then increment i by one
i = 0 + 0 //overwrite the value of i with 0 + 0
换句话说,你确实递增它但立即用旧值覆盖它。
这会分解为以下内部序列:
int temp1 = i; int temp2 = i + i; i = temp1 + 1; i = temp2;
(请记住,i ++是后递增的,而++ i是预先递增的 。)
您正在使用具有未定义行为的表达式。
具体而言,同一表达式中相同操作数的操作顺序没有明确定义。 混合使用递减运算符时,最终会得到令人惊讶的结果,因为编译器可以自由重新排列它们发生的顺序。
我将此视为一个Java问题,因为它确实有一个定义的结果。 我不时会受到启发,根据Java语言规范分析其中一个问题,特别是第15章 – 表达式 。
首先, i+=i++;
i
最初为0。
+=
是复合赋值运算符之一 。 对于int
i
,该语句等效于i = (int)(i+i++);
为了评估+
我们必须首先评估它的左侧,值为0.接下来我们评估它的右侧, i++
。
++
是Postfix Increment Operator 。 关键语句是“后缀增量表达式的值是存储新值之前变量的值。”,它是0.值1
作为副作用存储到i
,但这并不重要,因为那里在再次使用之前,它将是i
一项任务。
现在我们评估0 + 0。 +
是数字类型的加法运算符(+和 – )之一 。 这是一个很好的简单int添加,结果为0。
最后,我们通过执行转换为int
和赋值来完成+=
,就像它是Simple Assignment Operator =一样 ,不需要转换,因为右侧类型是int
。 i
现在0岁。
第二个语句是第一个使用+=
扩展且删除int
到int
转换的语句。 结果相同。
i = i + (i+1);
的差异i = i + (i+1);
是一个简单的加法结果, +
。 再次参见数值类型的加法运算符(+和 – ) :“二进制+运算符在应用于两个数值类型的操作数时执行加法,产生操作数的总和。”。 该语句为i
赋予0+(0+1)
。
将三个语句中的每一个视为等于i = i + (i + 1)
,得到1,3,7,完全在合理的C实现的可能性范围内。
Java评估顺序是从左到右 , 操作数在操作之前进行评估 。 这意味着当i=0
,评估如下:
i+=i+1 is equivalent to: i = i + (i++) evaluating operators from left to right and knowing i=0 i = 0 + (i++) evaluate i++. it returns 0 and assigns i=1 i = 0 + 0 evaluate 0+0=0 assign i=0, overwriting the previously assigned value of 1
Java代码中的第二个表达式与第一个表达式相同,但第三个表达式不同:
i = i + (i+1) given that i=0, we evaluate left to right: evaluate i as 0 and obtain: i = 0 + (i+1) evaluate i+1: evaluate i as 0 and obtain: 0+1=1 thus we obtain: i = 0 + 1 i = 1
注意(i + 1)没有副作用,它只返回i + 1的值但不改变i的值。
至于C代码,据我所知,这种情况下的执行顺序是未定义的,因此需要由编译器实现来决定。 我可能错了这个,所以请纠正我。
i ++将首先评估i,以便为什么在操作完成后得到0并且堆叠其后将增加的值。
所以,如果你这样做
int i = 0; System.out.println(i++); = 0 System.out.println(i++); = 1