Подтвердить что ты не робот

Почему "a ^ = b ^ = a ^ = b;" отличной от "a ^ = b; b ^ = a; 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;
4b9b3361

Ответ 1

Проблема заключается в порядке оценки:

См. раздел JLS 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  

Ответ 2

Поскольку a ^= b ^= a ^= b; анализируется как:

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

Что можно свести к:

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

Итак, b будет иметь значение b ^ (a ^ b) и, наконец, a будет a ^ (b ^ (a ^ b).

Ответ 3

Это очень похоже на запись в книге Блоха и блоха Блоха (см. главу 2, головоломка 7 ( "Swap Meat" ). Я не могу улучшить его.

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

Эта идиома использовалась на языке программирования C и оттуда его путь в С++, но не гарантированно работает в любом из этих языки. Гарантируется, что он не будет работать на Java. Язык Java спецификация говорит, что операнды операторов вычисляются слева направо [JLS 15.7]. Чтобы оценить выражение x ^= expr, значение x выбирается перед вычислением expr, а Исключительное ИЛИ этих двух значений присваивается переменной 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

Ответ 4

Проверьте приоритет оператора (ref: http://introcs.cs.princeton.edu/java/11precedence/ в результате поиска приоритета java-оператора). Здесь приведена ссылка на документацию Oracle (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html), хотя она немного плотная).

В частности, ^ = обрабатывается справа налево (не слева направо)

Ответ 5

Второй вариант равен

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

Ответ 6

У меня недостаточно очков репутации, чтобы прокомментировать ответ Натана.

@Ответ Натана Хьюза, говорящий о книге 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.