我发现此代码使用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
现在,我想知道,为什么我的代码输出不正确?
答案 0 :(得分:76)
要做的第一点是,显然你不应该使用这个代码。但是,当您展开它时,它将等同于:
j = j ^ (i = i ^ (j = j ^ i));
(如果我们使用更复杂的表达式,例如foo.bar++ ^= i
,那么++
只评估一次是很重要的,但在这里我相信它更简单。)
现在,操作数的评估顺序总是从左到右,所以首先我们得到:
j = 36 ^ (i = i ^ (j = j ^ i));
这(上图)是最重要的一步。我们最后以36作为最后执行的XOR操作的LHS。在对RHS进行评估后,LHS不是“j
的值”。
对^的RHS的评估涉及“一级嵌套”表达式,因此它变为:
j = 36 ^ (i = 25 ^ (j = j ^ i));
然后查看最深层次的嵌套,我们可以替换i
和j
:
j = 36 ^ (i = 25 ^ (j = 25 ^ 36));
...变成
j = 36 ^ (i = 25 ^ (j = 61));
首先在RHS中对j
进行分配,但结果最后会被覆盖,所以我们可以忽略这一点 - 在最终分配之前没有进一步评估j
:
j = 36 ^ (i = 25 ^ 61);
现在相当于:
i = 25 ^ 61;
j = 36 ^ (i = 25 ^ 61);
或者:
i = 36;
j = 36 ^ 36;
哪个成为:
i = 36;
j = 0;
我认为这一切都是正确的,并得到了正确答案......如果有关评估订单的一些细节略有偏离,请向Eric Lippert道歉:(
答案 1 :(得分:15)
检查生成的IL并给出不同的结果;
正确的交换产生了一个直截了当的:
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
很明显,第二种方法生成的代码是incorect,因为j的旧值用于需要新值的计算中。
答案 2 :(得分:7)
C#在堆栈上加载j
,i
,j
,i
,并存储每个XOR
结果而不更新堆栈,因此最左边{ {1}}使用XOR
的初始值。
答案 3 :(得分:0)
重写:
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);