Почему при использовании этой составной формы происходит замена значений с помощью XOR?
Я нашел этот код для замены двух чисел без использования третьей переменной с помощью оператора XOR ^
.
Код:
int i = 25;
int j = 36;
j ^= i;
i ^= j;
j ^= i;
Console.WriteLine("i:" + i + " j:" + j);
//numbers Swapped correctly
//Output: i:36 j:25
Теперь я изменил приведенный выше код на этот эквивалентный код.
Мой код:
int i = 25;
int j = 36;
j ^= i ^= j ^= i; // I have changed to this equivalent (???).
Console.WriteLine("i:" + i + " j:" + j);
//Not Swapped correctly
//Output: i:36 j:0
Теперь, я хочу знать, Почему мой код дает неверный вывод?
Ответы
Ответ 1
EDIT: Хорошо, понял.
Первое, что нужно сделать, - это то, что вы все равно не должны использовать этот код. Однако при его расширении он становится эквивалентным:
j = j ^ (i = i ^ (j = j ^ i));
(Если бы мы использовали более сложное выражение, такое как foo.bar++ ^= i
, было бы важно, чтобы ++
оценивался только один раз, но здесь я считаю это проще.)
Теперь порядок оценки операндов всегда слева направо, поэтому для начала получим:
j = 36 ^ (i = i ^ (j = j ^ i));
Это (выше) - это самый важный шаг. Мы закончили с 36 как LHS для операции XOR, которая выполняется последним. LHS не является "значением j
после оценки RHS".
Оценка RHS of ^ включает в себя "одноуровневое" выражение, поэтому оно становится:
j = 36 ^ (i = 25 ^ (j = j ^ i));
Затем, глядя на самый глубокий уровень вложенности, мы можем заменить как i
, так и j
:
j = 36 ^ (i = 25 ^ (j = 25 ^ 36));
... который становится
j = 36 ^ (i = 25 ^ (j = 61));
Сначала назначается назначение j
в RHS, но результат все равно перезаписывается в конце, поэтому мы можем игнорировать это: дальнейших оценок j
до окончательного назначения нет:
j = 36 ^ (i = 25 ^ 61);
Теперь это эквивалентно:
i = 25 ^ 61;
j = 36 ^ (i = 25 ^ 61);
Или:
i = 36;
j = 36 ^ 36;
Что будет:
i = 36;
j = 0;
Я думаю, что все правильно, и он подходит к правильному ответу... приносят извинения Эрику Липперту, если некоторые подробности об оценочном порядке слегка отключаются: (
Ответ 2
Проверял сгенерированный ИЛ и выдавал разные результаты;
Правильный своп создает просто:
IL_0001: ldc.i4.s 25
IL_0003: stloc.0 //create a integer variable 25 at position 0
IL_0004: ldc.i4.s 36
IL_0006: stloc.1 //create a integer variable 36 at position 1
IL_0007: ldloc.1 //push variable at position 1 [36]
IL_0008: ldloc.0 //push variable at position 0 [25]
IL_0009: xor
IL_000a: stloc.1 //store result in location 1 [61]
IL_000b: ldloc.0 //push 25
IL_000c: ldloc.1 //push 61
IL_000d: xor
IL_000e: stloc.0 //store result in location 0 [36]
IL_000f: ldloc.1 //push 61
IL_0010: ldloc.0 //push 36
IL_0011: xor
IL_0012: stloc.1 //store result in location 1 [25]
Неправильный своп генерирует этот код:
IL_0001: ldc.i4.s 25
IL_0003: stloc.0 //create a integer variable 25 at position 0
IL_0004: ldc.i4.s 36
IL_0006: stloc.1 //create a integer variable 36 at position 1
IL_0007: ldloc.1 //push 36 on stack (stack is 36)
IL_0008: ldloc.0 //push 25 on stack (stack is 36-25)
IL_0009: ldloc.1 //push 36 on stack (stack is 36-25-36)
IL_000a: ldloc.0 //push 25 on stack (stack is 36-25-36-25)
IL_000b: xor //stack is 36-25-61
IL_000c: dup //stack is 36-25-61-61
IL_000d: stloc.1 //store 61 into position 1, stack is 36-25-61
IL_000e: xor //stack is 36-36
IL_000f: dup //stack is 36-36-36
IL_0010: stloc.0 //store 36 into positon 0, stack is 36-36
IL_0011: xor //stack is 0, as the original 36 (instead of the new 61) is xor-ed)
IL_0012: stloc.1 //store 0 into position 1
Очевидно, что код, сгенерированный во втором методе, используется, поскольку старое значение j используется в вычислении, где требуется новое значение.
Ответ 3
С# загружает j
, i
, j
, i
в стек и сохраняет каждый результат XOR
без обновления стека, поэтому самый левый XOR
использует начальное значение для j
.
Ответ 4
Переписывая:
j ^= i;
i ^= j;
j ^= i;
Расширение ^=
:
j = j ^ i;
i = j ^ i;
j = j ^ i;
Замена:
j = j ^ i;
j = j ^ (i = j ^ i);
Заменить это работает только если /, потому что левая часть оператора ^ оценивается сначала:
j = (j = j ^ i) ^ (i = i ^ j);
Свернуть ^
:
j = (j ^= i) ^ (i ^= j);
Симметрично:
i = (i ^= j) ^ (j ^= i);