为什么“a ^ = b ^ = a ^ = b;”不同于“a ^ = b; b ^ = A; 一个^ = B;”?

我尝试了一些代码来交换Java中的两个整数而不使用第三个变量,使用XOR。

以下是我尝试的两个交换函数:

package lang.numeric; public class SwapVarsDemo { public static void main(String[] args) { int a = 2984; int b = 87593; swapDemo1(a,b); swapDemo2(a,b); } private static void swapDemo1(int a, int b) { a^=b^=a^=b; System.out.println("After swap: "+a+","+b); } private static void swapDemo2(int a, int b) { a^=b; b^=a; a^=b; System.out.println("After swap: "+a+","+b); } } 

此代码生成的输出如下:

 After swap: 0,2984 After swap: 87593,2984 

我很想知道,为什么这句话:

  a^=b^=a^=b; 

与此不同?

  a^=b; b^=a; a^=b; 

问题是评估顺序:

见JLS第15.26.2节

首先,评估左侧操作数以产生变量。 如果此评估突然完成,则赋值表达式出于同样的原因突然完成; 不评估右侧操作数,也不进行赋值。

否则,保存左侧操作数的值,然后评估右侧操作数。 如果此评估突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何分配。

否则,左侧变量的保存值和右侧操作数的值用于执行复合赋值运算符指示的二进制运算。 如果此操作突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何赋值。

否则,二进制操作的结果将转换为左侧变量的类型,经过值集转换(第5.113节)到相应的标准值集(不是扩展指数值集),结果转换的内容存储在变量中。

你的表达式如下:

a^=b^=a^=b;

  1. 评估a
  2. 评估b^=a^=b
  3. xor这两个(所以步骤1中的a还没有应用^=b
  4. 将结果存储在a

换句话说,您的表达式等同于以下java代码:

  int a1 = a; int b2 = b; int a3 = a; a = a3 ^ b; b = b2 ^ a; a = a1 ^ b; 

您可以从方法的反汇编版本中看到:

  private static void swapDemo1(int, int); Code: 0: iload_0 1: iload_1 2: iload_0 3: iload_1 4: ixor 5: dup 6: istore_0 7: ixor 8: dup 9: istore_1 10: ixor 11: istore_0 

因为a ^= b ^= a ^= b; 解析如下:

 a ^= (b ^= (a ^= b)); 

哪个可以简化为:

 a ^= (b ^= (a ^ b)); 

所以b将具有值b ^ (a ^ b)并且最终a将是a ^ (b ^ (a ^ b)

这与Bloch和Gafter的Java Puzzlers一书中的条目非常相似,参见第2章,谜题7(“Swap Meat”)。 我无法改进它。

解决方案中的解释是:

这个习惯用法在C编程语言中使用,并从那里进入C ++,但不保证可以在这两种语言中使用。 保证不能在Java中工作。 Java语言规范说运算符的操作数从左到右进行评估 [JLS 15.7]。 为了计算表达式x ^= expr ,在计算expr之前对x的值进行采样,并将这两个值的异或分配给变量x [JLS 15.26.2]。 在CleverSwap程序中,变量x对表达式中的每个外观进行两次采样 – 但两次采样都在任何赋值之前进行。 以下代码片段更详细地描述了断开的交换习惯用法的行为,并解释了我们观察到的输出:

上面引用中引用的代码是:

 // The actual behavior of x ^= y ^= x ^= y in Java int tmp1 = x; // First appearance of x in the expression int tmp2 = y; // First appearance of y int tmp3 = x ^ y; // Compute x ^ y x = tmp3; // Last assignment: Store x ^ y in x y = tmp2 ^ tmp3; // 2nd assignment: Store original x value in y x = tmp1 ^ y; // First assignment: Store 0 in x 

检查运算符优先级(参考: http : //introcs.cs.princeton.edu/java/11precedence/ ,搜索java运算符优先级)。 此处显示了对Oracle文档的引用( http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html ),尽管它有点密集)。

特别是,^ =从右到左处理(不是从左到右)

第二个变体等于

a=a^(b^(a^b));

我没有足够的声誉来评论内森的回答。

@Nathan Hughes回应谈论Java Puzzlers的书是正确的,他的回答指向了一些见解,这些见解确实允许你在一行中做到这一点。 虽然没有OP的问题那么优雅。

正如内森指出:

在CleverSwap程序中,变量x对表达式中的每个外观进行两次采样 – 但两次采样都在任何赋值之前进行

除了使用https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html作为指南之外,您还可以通过以下方式执行以下操作:

 a = ( b = (a = a ^ b) ^ b) ^ a; 

关键是将变量值赋值为第一个异或的一部分,确保将其保留在第二个异或的左侧(参见上面的jls链接,左手操作数中的一个很好的赋值示例)。 同样,将b变量设置为第二个XOR的结果,再次保持在最终XOR的左侧。