"不使用第三个变量交换两个变量的值"可能存在潜在问题。

16

我最近发现了一种不使用第三个变量就可以交换两个变量值的方法。

a^=b^=a^=b

但是当我在不同的编译器上尝试上述代码时,我得到了不同的结果,有些正确,有些不正确。

这段代码有什么严重的问题吗?


19
当然,实际上不会这样交换。使用临时变量,这样更干净,并且在现代硬件上执行速度更快。 - GManNickG
17
@Prasoon: 我已经厌倦了那些关于“我懂Java/C#,试着学习C++,在Java/C#中我们…”的问题。答案就是停下来读一本书。如果你不懂C++初学者,只有通过阅读初学者C++书籍来学习。没有捷径可走。试图通过另一种语言的“知识”来猜测是愚蠢的,就像因为你知道如何骑三轮车而试图驾驶飞机一样。 - GManNickG
6
@Paul: 不是的,Pattrick在这里提出的问题不同。他并非在询问“如何在不使用第三个变量的情况下交换两个变量的值”。 - Prasoon Saurav
5
我同意,“为什么A不起作用?”这个问题不应该被关闭为“如何做B?”并给出C作为解决方案的重复问题。 - Georg Fritzsche
3
@GMan:让我引用一位 MetaSO 用户的评论,我觉得这个评论完美地概括了 SO 的作用:“这是一个问答网站,不是一个‘复杂问题、深刻答案让你成长’的网站。人们应该能够提出简单的问题并得到简单的答案,这些答案可能只是被直接提供的。很多人只想编写可行的代码,而不是变得更有能力。”– Owen Sep 19 '08 at 21:23" - RCIX
显示剩余10条评论
8个回答

38

这段代码有什么严重的问题吗?

有!

a^=b^=a^=b 实际上会在C和C++中引发未定义的行为,因为您尝试在两个序列点之间多次更改 a 的值。


尝试编写以下代码(虽然并不完全可靠)

a ^= b;
b ^= a;
a ^= b;

使用b ^= a ^= b ^= a 来交换两个变量的值。

P.S: 永远不要尝试在不使用第三个变量的情况下交换两个变量的值。始终使用第三个变量。

编辑:

正如@caf所指出的那样,b^=a^=b是可以的,即使^=运算符的参数计算顺序未指定,由于表达式中对b的所有访问都用于计算存储在b中的最终值,因此行为是良好定义的。


1
b的修改实际上是可以的,因为它只是读取来确定新值(b ^= a ^= b;也是可以的)。 - caf
5
@caf:但是=运算符的参数计算顺序是未指定的。 - Prasoon Saurav
2
@crypto:因为这种交换方法只适用于整数。 :-) - Prasoon Saurav
1
@Prasoon, 由于上述原因,这是未定义行为。a = b 不会按顺序计算相对于b的值。无论 b 是否包含对 a 自身的已排序副作用都没有关系。它们必须相对于 a = b 左操作数的值计算进行排序才能有效。 - Johannes Schaub - litb
1
b ^= a ^= b; 的定义和 i = a[i]; 一样,原因是它们都是定义有意义的。虽然 b(a ^= b) 的求值顺序未指定,但在计算新值之前,两者都必须被求值。 - caf
显示剩余15条评论

15

如果你正在使用C ++,为什么不使用STL中的swap算法?它非常适合这个目的,并且很清楚它的作用:

#include <algorithm>
using namespace std;

// ...

int x=5, y=10;    // x:5 y:10
swap(x,y);        // x:10 y:5

2
公平地说,这个问题也被标记为C语言。 - GManNickG
@GMan:说得好!我已经相应地编辑了我的答案。谢谢。 - Component 10

5

我建议你在c++中使用std::swap()。

对于c语言,使用这个宏。注意,你需要先比较a和b,否则当它们指向同一内存位置时,你会擦除该值并将其变为0。

#define swap(a, b)  ((a) == (b) || (a) ^= (b), (b) ^= (a), (a) ^= (b))

a == b 时,你如何计算最终结果为 0?最初,a = b = X。执行 a ^= b 后,a == 0。执行 b ^= a 后,b == X。最后,执行 a ^= b,则 a == X && b == X。这正是我们所期望的。 - Phil Miller
@Novelocrat,我指的是一种特殊情况,即a和b是同一内存位置时,您会清除该值。例如,int a = 5; int&b = a; 我编辑了我的答案以澄清此问题。 - grokus
解决方案是检查(&a==&b)。 - flownt
@flownt,当然你可以这样做,但是即使a和b指向的地址不同但值相同,为什么还要费心去交换呢? - grokus

5

基于 R. & sellibitze 的贡献:

使用逗号运算符:

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

来自文本和维基百科:

逗号运算符可用于将相关表达式链接在一起。逗号链接的表达式列表从左到右进行评估,最右侧表达式的值是组合表达式的值。它充当一个序列点。

序列点保证了所有先前评估的副作用都已经执行,并且尚未执行后续评估的任何副作用。它消除了原始表达式执行顺序不明确而产生的未定义行为。


1
那并没有帮助。原始语句与您的语句等效,两者都具有未定义的行为。 - R.. GitHub STOP HELPING ICE
1
我认为已编辑的版本应该是可行的,因为逗号是一个序列点。 - sellibitze
2
我认为这仍然是未定义的行为。它相当于 a = a ^ (a ^= b, b ^= a);,而 ^ 运算符的左侧可能在括号内表达式之前或之后被计算。 - R.. GitHub STOP HELPING ICE
2
当前版本已经定义。然而,与使用本地临时变量相比,它在现代CPU上的速度仍然很慢(当然,本地临时变量会被优化为寄存器)。 - Donal Fellows
@R..:版本已定义,因为逗号运算符有自己的序列点。a ^= b被完全评估,然后是b ^= a,然后是a ^= b。由于所有这些都是明确定义的,使用逗号运算符将它们串在一起也是明确定义的。当然,这并不适用于分隔函数参数的类似逗号。 - David Thornley
@David:自从我发表评论以来,答案已经再次编辑过了,从我的评论内容就可以看出来。 - R.. GitHub STOP HELPING ICE

3

按照以下方式进行:

a ^= b;
b ^= a;
a ^= b;

这个答案有一个错误,请看我的答案。 - grokus

1

这个怎么样?

a = a + b;
b = a - b;
a = a - b;

0
你也可以尝试以下这个方法,但如果数字太大,值会溢出。
a=a*b;
b=a/b;
a=a/b;

0

我想知道为什么没有人建议给表达式加括号。看起来这不再是未定义行为了。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接