Почему "a^=b^=a^=b" отличается от "a^=b; b^=a; A^=b;"?

я попробовал код для замены двух целых чисел на Java без использования 3-й переменной, используя 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;

6 ответов


вопрос в порядке оценки:

посмотреть JLS раздел 15.26.2

во-первых, левый операнд вычисляется для создания переменной. если эта оценка завершается резко, то выражение присвоения завершается резко по той же причине; правый операнд не оценено и назначение не происходит.

в противном случае-значение левого операнда сохраняется, и затем вычисляется правый операнд. Если эта оценка завершится резко, то выражение присвоения завершается резко по той по той же причине, и никакого назначения не происходит.

в противном случае, сохраненное значение левой переменной и значение правый операнд используется для выполнения двоичной операции указывается оператором составного назначения. Если эта операция завершается резко, то выражение присвоения завершается резко по той же причине и нет происходит назначение.

в противном случае результат двоичной операции преобразуется в тип левой переменной, подвергнутой преобразованию набора значений (§5.1.13) к соответствующему стандартному значению (не расширенному значению экспоненты set), и результат преобразования сохраняется в переменную.

таким образом, ваше выражение делает:

a^=b^=a^=b;

  1. оценить a
  2. оценить b^=a^=b
  3. xor два (так что 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).


Это очень похоже на запись в Блоха и Гафтера Java Puzzlers книга, см. Главу 2, головоломка 7 ("поменять мясо"). Я не могу улучшить его.

объяснение в решении:

эта идиома была использована в языке программирования C и оттуда сделана его путь в C++, но не гарантируется работа в любом из них языки. Он гарантированно не будет работать на Java. язык Java спецификация говорит, что операнды операторы оцениваются слева направо [JLS 15.7]. Чтобы оценить выражение x ^= expr, значение x отбирается перед оценкой expr, и исключительное или из этих двух значений присваивается переменной x [JLS 15.26.2]. В программе CleverSwap переменная x выборка дважды-один раз для каждого внешнего вида в выражении-но оба выборки происходят перед любыми назначениями. Следующий фрагмент кода описывает поведение сломанной идиомы swap в подробнее и объясняет вывод, который мы наблюдали:

код, указанный в приведенной выше цитате:

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

Проверьте приоритет оператора (ref: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 находится на месте, и его ответ указывает на некоторые идеи, которые позволяют вам сделать это в 1 строке. Хотя и не так элегантно, как вопрос ОП.

Как указывает Натан:

в программе CleverSwap переменная x отбирается дважды-один раз для каждого появления в выражении,но оба выборки происходят до любые задания

а также использование https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html в качестве руководства, вы можете сделать это в 1 строке с:

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

ключ, являющийся назначением значения переменной a как части первого XOR, гарантирующего, что вы сохраняете это в левой части второго XOR (см. ссылку jls выше, для хорошего примера назначения в левом операнде). Аналогично, установка переменной b с результатами второй КСОР, снова держась слева от последнего КСОРА.