一行上的多个分配未按预期工作

我试图交换两个s - 在示例中,并在没有库函数的情况下在一行中执行此操作。intxy

所以我从这个开始:

int x = 4;
int y = 3;

System.out.println(x);
System.out.println(y);

x ^= y;

System.out.println(x);
System.out.println(y);

y ^= x;

System.out.println(x);
System.out.println(y);

x ^= y;

System.out.println(x);
System.out.println(y);

输出符合预期。到目前为止,一切都很好。4, 3, 7, 3, 7, 4, 3, 4

接下来是这个:

int x = 4;
int y = 3;

System.out.println(x);
System.out.println(y);

y ^= (x ^= y);

System.out.println(x);
System.out.println(y);

x ^= y;

System.out.println(x);
System.out.println(y);

输出再次如预期的那样。到目前为止还是不错的。4, 3, 7, 4, 3, 4

最后是这个:

int x = 4;
int y = 3;

System.out.println(x);
System.out.println(y);

x ^= (y ^= (x ^= y));

System.out.println(x);
System.out.println(y);

在此阶段,输出变为 。现在我知道这是因为当时作业没有完成的结果 - 为什么会发生这种情况?为什么实际不赋值到变量,以便它成为最后一个赋值?4, 3, 0, 404 ^ 4xx ^= y7x7 ^ 4


答案 1

让我们尝试扩展您的上一个表达式。

它评估为,

x = x^(y = y^ (x = x^y));

请注意,表达式是从计算的,

它变成了,

x = 4 ^ (y = 3 ^ (x = 4 ^ 3));

现在,问题已经变得很明显。右?

编辑:

为了澄清困惑,让我试着解释一下我从左到右评估的意思。

int i = 1;
s = i + (i = 2) + i;

现在,表达式的计算结果为:

s = 1 +    2    + 2;

请注意,在赋值的左侧是 ,但在赋值的右侧(以及在 assigment 上)被计算为 ,因为求值是从左到右,当涉及到表达式的第 2 部分和第 3 部分时,s 值为 。i12i2


答案 2

评估顺序在JLS的第15章中定义。项目15.7.1 说:

如果运算符是复合赋值运算符 (§15.26.2),则对左侧操作数的计算包括记住左侧操作数表示的变量,以及获取和保存该变量的值以在隐含的二进制操作中使用。

为了进一步解释,他们有两个涉及赋值的计算示例。此处的赋值位于运算符的左侧:

int i = 2;
int j = (i=3) * i;
System.out.println(j);

他们特别说结果是9,不允许是6。也就是说,(i=3)既计算为3,又i在与自身相乘之前被分配为3。

但在第二个例子中:

    int a = 9;
    a += (a = 3);  // first example
    System.out.println(a);
    int b = 9;
    b = b + (b = 3);  // second example
    System.out.println(b);

JLS 指定两个打印都应生成 12 个,并且不允许生成 6 个。也就是说,因为对 b 的赋值位于右侧,所以左侧的值(或操作中隐含的左侧)的值会首先获取并保存该赋值之前的值,然后才执行括号内的操作。ba+=

在内部,表达式被分解为JVM操作,这些操作将值推送和弹出到“操作数堆栈”上。如果你这样想 - 在 中,首先将 的值推送到操作数堆栈上,然后执行 (b=3),然后将其值添加到从堆栈中弹出的值(b 的旧值)中,这将是有意义的。此时,左手仅代表“b被推送到堆栈上时的值是什么”,而不是“b的当前值”。b = b + (b=3)bb