为什么这个表达式i + = i ++与Java和C不同?

Why does this expression i+=i++ differs from Java and C?

我知道前缀和posfix操作…我和我之间的区别。

但我想我错过了一些东西。您可以在下面找到代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

    }
}

所以输出是:

1
2
3
0
0
1

我在C中尝试了同样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>

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
2
3
1
3
7

为什么i+=i++不增加i,而C中的相同代码增加值?


爪哇

在Java中,表达式具有明确的含义。复合赋值运算符的规范说明如下:

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

...

At run time, the expression is evaluated in one of two ways. If the left-hand operand expression is not an array access expression, then four steps are required:

  • First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression
    completes abruptly for the same reason; the right-hand operand is not
    evaluated and no assignment occurs.
  • Otherwise, the value of the left-hand operand is saved and then the right-hand operand is evaluated. If this evaluation completes
    abruptly, then the assignment expression completes abruptly for the
    same reason and no assignment occurs.
  • Otherwise, the saved value of the left-hand variable and the value of the right-hand operand are used to perform the binary operation
    indicated by the compound assignment operator. If this operation
    completes abruptly, then the assignment expression completes abruptly
    for the same reason and no assignment occurs.
  • Otherwise, the result of the binary operation is converted to the type of the left-hand variable, subjected to value set conversion
    (§5.1.13) to the appropriate standard value set (not an
    extended-exponent value set), and the result of the conversion is
    stored into the variable.

所以

1
i += i++;

等于

1
i = i + i++;

因为左侧的值在开始时被保存,然后添加到右侧的值中,并且由于Java中的表达式求值是向右的,所以由EDCOX1 OR 2 6引起的EDCOX1×6的修改被随后的赋值覆盖。

所有这些都意味着

1
i += i++;

等于

1
i += i;

C

在c中,表达式

1
i += i++;

具有未定义的行为。所以当你执行这段代码的时候,任何事情都可能发生。这意味着您无法预测语句完成后i将具有什么值。

因此,C程序的输出与Java程序的输出不同,这是完全可以预料到的。


Why does this expression i+=i++ differs from Java and C?

i+=i++i = i + i++相同

在Java语言中(和C语言)需要从左到右进行计算。即:

1
2
3
4
5
6
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中,这是可能的,但不要求从左到右计算。特别是任务

1
left = i;

1
2
incr = right + 1;
i = incr;

可以按其他顺序进行。也就是说,此命令在C中是合法的:

1
2
3
4
5
6
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

换言之,您确实会增加它,但会立即用旧值覆盖它。


这可分为以下内部顺序:

1
2
3
4
int temp1 = i;
int temp2 = i + i;
i = temp1 + 1;
i = temp2;

(记住,i++是后增量,而+i是预增量。)


我把这个看作是Java问题,因为它确实有一个定义的结果。有时,我会从Java语言规范中,特别是第15章——表达式来分析这些问题中的一个。

首先,i+=i++;i最初为0。

+=是一种复合赋值算子。对于inti,该语句等价于i = (int)(i+i++);

要评估+,必须首先评估其左侧值0。接下来我们评估它的右手边,i++

++是后缀增量运算符。key语句是"postfix增量表达式的值是存储新值之前变量的值。",即0。值1作为副作用存储到i中,但这并不重要,因为在再次使用之前,将有一个分配给i的值。

现在我们计算0+0。+是数字类型的加法运算符之一(+和-)。这是一个很好的简单int加法,结果为0。

最后,我们通过对int执行强制转换和赋值(就好像它是简单的赋值运算符一样)来完成+=,不需要转换,因为右侧类型是inti现在为0。

第二个声明是第一个声明,扩展了+=,并且减少了intint的转换。同样的结果。

i = i + (i+1);的区别在于简单添加+的结果。请参见加法运算符(+和-)以了解数值类型:"当应用于数值类型的两个操作数时,binary+运算符执行加法,生成操作数之和。"声明将0+(0+1)分配给i

将这三条语句中的每一条都视为等同于i = i + (i + 1),得到1、3、7,这是合理的C实现的可能性。


Java评估顺序是从左到右,在操作之前对操作数进行评估。这意味着当i=0时,评估如下:

1
2
3
4
5
6
7
8
9
10
11
12
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代码中的第二个表达式等价于第一个表达式,但第三个表达式不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
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,并将其值叠加在一起。

所以如果你这样做

1
2
3
int i = 0;
System.out.println(i++); = 0
System.out.println(i++); = 1