有人能解释一下如何在没有临时变量的情况下使用异或交换两个变量吗?
void xorSwap (int *x, int *y)
{
if (x != y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
我明白它做什么,但能有人为我解释一下它的工作原理吗?
有人能解释一下如何在没有临时变量的情况下使用异或交换两个变量吗?
void xorSwap (int *x, int *y)
{
if (x != y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
我明白它做什么,但能有人为我解释一下它的工作原理吗?
通过进行替换操作,您可以看到它是如何工作的:
x1 = x0 xor y0
y2 = x1 xor y0
x2 = x1 xor y2
替换操作,
x1 = x0 xor y0
y2 = (x0 xor y0) xor y0
x2 = (x0 xor y0) xor ((x0 xor y0) xor y0)
y2 = x0 xor (y0 xor y0)
x2 = (x0 xor x0) xor (y0 xor y0) xor y0
由于任何x的异或操作与自身的结果都是0,
y2 = x0 xor 0
x2 = 0 xor 0 xor y0
由于对于任意x,x异或0 == x
,
y2 = x0
x2 = y0
交换已完成。
y2
改成 y1
。你用了 x0
和 x1
,但是却用了 y0
和 y2
,这让我感到有些不舒服。:-] - Frerich Raabe其他人已经解释过了,现在我想解释一下为什么这原本是个好主意,但现在不再是。
回到以前我们使用简单的单周期或多周期CPU时,使用这个技巧可以避免昂贵的内存引用或将寄存器溢出到堆栈。然而,现在我们有了具有大规模流水线的CPU。P4的流水线阶段从20到31个不等,任何读写寄存器之间的依赖都可能导致整个流水线停顿。异或交换在A和B之间存在一些非常严重的依赖关系,实际上并不重要,但会导致流水线停顿。停顿的流水线会导致代码执行缓慢,如果这个交换在你的内部循环中,你的执行速度将会非常慢。
一般情况下,当你使用一个临时变量进行交换时,你的编译器可以推断出你真正想做的事情,并将其编译为一条单独的XCHG指令。使用异或交换会使编译器更难猜测你的意图,因此更不太可能正确地对其进行优化。更不用说代码维护等问题了。
我更喜欢以图形的方式而不是数字的方式来思考。
假设你从x=11和y=5开始(我将使用一个假想的4位机器),以下是它们的二进制表示:
x: |1|0|1|1| -> 8 + 2 + 1
y: |0|1|0|1| -> 4 + 1
对我来说,异或操作是一个反转操作,连续执行两次则相当于镜像:
x^y: |1|1|1|0|
(x^y)^y: |1|0|1|1| <- ooh! Check it out - x came back
(x^y)^x: |0|1|0|1| <- ooh! y came back too!
以下这篇文章应该较易理解:
int x = 10, y = 7;
y = x + y; //x = 10, y = 17
x = y - x; //x = 7, y = 17
y = y - x; //x = 7, y = 10
x + y - ((x + y) - x) == x
因此:
x ^ y ^ ((x ^ y) ^ x) == x
它能够工作的原因是因为异或运算不会丢失信息。如果你可以忽略溢出,你也可以通过普通的加减法来完成同样的事情。例如,如果变量对A,B最初包含值1,2,你可以像这样交换它们:
// A,B = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
顺便说一下,有一个古老的技巧可以用单个“指针”编码双向链表。假设您有一个地址分别为A、B和C的内存块列表。每个块中的第一个单词分别是:
// first word of each block is sum of addresses of prior and next block
0 + &B // first word of block A
&A + &C // first word of block B
&B + 0 // first word of block C
如果你能访问块A,它会给你B的地址。要到达C,你需要用B中的“指针”减去A,以此类推。反过来也一样有效。要沿着列表运行,你需要保留两个相邻块的指针。当然,你可以使用异或代替加法/减法,这样就不必担心溢出问题。如果你想要一些乐趣,可以将其扩展到“链接网络”。int
溢出是UB) - chux - Reinstate Monica大多数人会使用临时变量来交换两个变量x和y的值,像这样:
tmp = x
x = y
y = tmp
这是一个巧妙的编程技巧,可以在不需要临时变量的情况下交换两个值:
a, b = b, a
x = x xor y
y = x xor y
x = x xor y
第一行中,我们使用异或操作将x和y组合在一起得到“混合值”,并将其存储回x中。异或是保存信息的好方法,因为您可以通过再次进行异或来删除它。
在第二行中,我们用y异或混合值,这会取消掉所有y的信息,只留下x。我们将此结果存回y中,现在它们已经交换了。
在最后一行中,x仍然具有混合值。我们再次用y(现在是x的原始值)进行异或操作,以将混合值中所有x的痕迹消除。这让我们只剩下y,交换完成!
计算机实际上有一个隐含的“临时”变量,它在将中间结果写回寄存器之前存储这些值。例如,如果您将3添加到寄存器中(在机器语言伪代码中):
ADD 3 A // add 3 to register A
ALU(算术逻辑单元)实际上执行指令3 + A。它获取输入(3、A)并创建结果(3 + A),然后CPU将其存储回A的原始寄存器中。因此,在得到最终答案之前,我们将ALU用作临时刮擦空间。@VonC 是正确的,这是一个巧妙的数学技巧。想象一下4位二进制数,看看这是否有助于理解。
word1 ^= word2;
word2 ^= word1;
word1 ^= word2;
word1 word2
0101 1111
after 1st xor
1010 1111
after 2nd xor
1010 0101
after 3rd xor
1111 0101
XOR方法基本上有3个步骤:
a’ = a XOR b (1)
b’ = a’ XOR b (2)
a” = a’ XOR b’ (3)
为了理解为什么这个方法有效,首先需要注意以下几点:
在第一步之后,a的二进制表示将只在a和b具有对立位的位置上具有1位。即(ak=1, bk=0)或(ak=0, bk=1)。现在,当我们在第二步中进行替换时,我们得到:
b’ = (a XOR b) XOR b
= a XOR (b XOR b) 因为XOR是可结合的
= a XOR 0 根据[4]上面的定义
= a 根据XOR的定义(参见上面的1)
现在我们可以将其代入第三步:
a” = (a XOR b) XOR a
= (b XOR a) XOR a 因为XOR是可交换的
= b XOR (a XOR a) 因为XOR是可结合的
= b XOR 0 根据[4]上面的定义
= b 根据XOR的定义(参见上面的1)
更详细的信息请参阅这里: 必要和充分
a = a + b
b = a - b ( = a + b - b once expanded)
a = a - b ( = a + b - a once expanded).
上面提到的内容表述不够清晰。
同样的推理也适用于异或交换:a ^ b ^ b = a 和 a ^ b ^ a = a。由于异或是可交换的,x ^ x = 0 和 x ^ 0 = x,这很容易理解,因为
= a ^ b ^ b
= a ^ 0
= a
and
= a ^ b ^ a
= a ^ a ^ b
= 0 ^ b
= b
我只是想加入一些数学解释,以使答案更完整。在群论中,XOR是一个阿贝尔群,也称为交换群。这意味着它满足五个要求:封闭性、结合性、单位元、逆元和交换律。
XOR交换公式:
a = a XOR b
b = a XOR b
a = a XOR b
扩展公式,用先前的公式替换a、b:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
可交换性意味着"a XOR b"等于"b XOR a":
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
结合律指的是"(a XOR b) XOR c"等同于"a XOR (b XOR c)":
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
= a
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
= b XOR 0
= b
你可以在群论中获取更多信息。