为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?

Posted

技术标签:

【中文标题】为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?【英文标题】:Why is "a^=b^=a^=b;" different from "a^=b; b^=a; a^=b;"?为什么是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”? 【发布时间】:2014-03-29 10:40:39 【问题描述】:

我尝试了一些代码来交换 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;

【问题讨论】:

我认为你被 java 的值传递搞砸了。 @EvertonAgner 我看不出传递值与问题有什么关系。究竟在哪里认为传递值会导致问题? 很公平,我忽略了这一点。 当您以这种方式“交换”值时,java 不是实际上创建了隐式中间对象吗?我认为使用临时变量应该可以提高性能。通常应该尝试使用按位运算来提高性能,仅在没有自动 GC 的语言中。 【参考方案1】:

问题是评估的顺序:

见JLS section 15.26.2

首先,计算左侧操作数以生成变量。 如果 此评估突然完成,然后赋值表达式 出于同样的原因突然完成;右手操作数不是 评估并且没有分配发生。

否则,左边操作数的值被保存,然后 评估右手操作数。如果此评估完成 突然,然后赋值表达式突然完成 同样的原因,没有分配发生。

否则,左侧变量的保存值和 右手操作数用于执行二元运算 由复合赋值运算符指示。如果这个操作 突然完成,然后赋值表达式突然完成 出于同样的原因,没有分配发生。

否则,二进制运算的结果被转换为类型 左侧变量的值,经过值集转换(第 5.1.13 节) 到适当的标准值集(不是扩展指数值 set),并将转换结果存入变量中。

所以你的表达方式是:

a^=b^=a^=b;

    评估a 评估b^=a^=b xor 两者(所以第一步中的a 还没有应用^=b) 将结果存储在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  

【讨论】:

+1 - 我倾向于跳上使用反汇编字节码作为 Java 行为“解释”的答案。但是你做对了……你在 JLS 中找到了真正的解释,并使用字节码来确认解释。 很好的答案,但我很好奇 Java 是否允许您在这种情况下使用括号强制执行所需的行为。 a^=(b^=(a^=b)); 有效吗? @Patrick_M 它没有任何区别(您描述的已经是运算符优先级的工作方式),因为左侧仍然在右侧之前评估。【参考方案2】:

因为a ^= b ^= a ^= b; 被解析为:

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

可以简化为:

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

所以b 的值将是b ^ (a ^ b),最后a 将是a ^ (b ^ (a ^ b)

【讨论】:

解释为什么你做的减少是正确的——参考语言规范和运算符优先级信息。提问者很可能会遇到处理运算符优先级的其他问题(在这种语言和其他语言中)。 简化表示在最里面的括号a ^= b 等于a ^ b。这是正确的,因为分配给变量a 的值不会在其余的评估中使用。相反,该值稍后会被最外层括号评估的结果覆盖。不需要语言规范...【参考方案3】:

这与 Bloch 和 Gafter 的 Java Puzzlers 书中的条目非常相似,请参阅第 2 章,谜题 7(“交换肉”)。我无法改进它。

解决方案中的解释是:

这个成语被用在 C 编程语言中,并由此产生 它进入 C++,但不能保证在其中任何一个中工作 语言。保证不能在 Java 中工作。 Java 语言 规范说 运算符的操作数从左到右计算 [JLS 15.7]。要评估表达式x ^= exprx 的值在计算 expr 之前被采样,并且 这两个值的异或分配给变量x [JLS 15.26.2]。在 CleverSwap 程序中,变量 x 被采样两次——表达式中的每次出现一次——但两次采样 发生在任何分配之前。以下代码 sn -p 描述了 更详细地解释了损坏的交换习语的行为并解释了 我们观察到的输出:

上面引用的代码是:

// 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

【讨论】:

【参考方案4】:

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

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

【讨论】:

你应该为这些基本的东西指向正确的 Java 语言文档。此外,您应该对问题进行解释,而不仅仅是指向外部文档。【参考方案5】:

我没有足够的声望点来评论 Nathan 的回答。

@Nathan Hughes 对 Java Puzzlers 这本书的回应恰到好处,他的回答指出了一些可以让您在 1 行中做到这一点的见解。虽然不如 OP 的问题那么优雅。

正如内森指出的那样:

在 CleverSwap 程序中,变量 x 被采样两次——每次出现在表达式中一次——但两次采样都发生在任何赋值之前

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

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

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

【讨论】:

【参考方案6】:

第二个变体等于

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

【讨论】:

以上是关于为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥条件运算符是右结合的?

c++中为啥赋值运算符重载返回类型是引用

MySQL联合索引生效的条件、索引失效的条件

[我也不知道来源是哪] 一道数论题

为啥列表中允许使用尾随逗号?

不借助第三个变量交换两个数值类型变量的值