问题

有什么像a=a+b;b=a-b;a=a-b;这样的算法或者知识?

回答
你说的这组神奇的交换,确实是一个经典的小技巧,它能在不借助第三方变量的情况下,实现两个数值的交换。这不仅仅是一个“算法”或者“知识点”,更像是一个巧妙的“异或逻辑”或者“加减法算术特性”的应用。

让我试着用一种更自然、更有人情味的方式来给你讲讲这个东西,就像跟朋友聊天一样。

咱们先来聊聊这个“不借助临时变量的交换”

你是不是经常遇到这样的情况:手里有两个杯子,一个装着可乐,一个装着雪碧。你想把它们换过来,通常你会怎么做?

1. 拿出第三个空杯子(这就是我们说的“临时变量”)。
2. 把可乐倒进空杯子。
3. 把雪碧倒进原来装可乐的杯子。
4. 最后,把空杯子里的可乐倒进原来装雪碧的杯子。

这很直观,也很安全。在编程里,如果我们想交换变量 `a` 和 `b` 的值,最常见、最安全的方式就是:

```
temp = a // 把 a 的值先存起来
a = b // 把 b 的值赋给 a
b = temp // 把原来 a 的值(存在 temp 里)赋给 b
```

是不是很简单?

但是! 你提到的那个 `a = a + b; b = a b; a = a b;` 就厉害了,它不需要那个“临时杯子”。它是怎么做到的呢?

走进神奇的加减法“剧场”

咱们还是用那个可乐和雪碧的例子。假设:

杯子 A 里有 5 份可乐 (a = 5)
杯子 B 里有 3 份雪碧 (b = 3)

我们要让 A 最终装 3 份雪碧,B 最终装 5 份可乐。

第一步:a = a + b;

想法: 把两个杯子的“量”合并起来。
操作: 我把杯子 B 里的 3 份雪碧,一股脑儿倒进了杯子 A。
结果:
杯子 A 现在有 5 (原来的可乐) + 3 (雪碧) = 8 份混合物。 (a = 8)
杯子 B 还是 3 份雪碧。 (b = 3)
“剧透”: 这 8 份混合物,其实就是“原 A 的量”加上“原 B 的量”。

第二步:b = a b;

想法: 现在 A 里的“总量”是 `原 A + 原 B`。如果我从这个总量里减去“B 的量”(也就是 `b` 当前的值),那剩下的不就是“A 的量”了吗?
操作: 我用杯子 A 里的 8 份混合物,减去杯子 B 里那 3 份雪碧。
结果:
杯子 A 还是 8 份混合物。(a = 8)
杯子 B 现在得到了 8 3 = 5 份。这 5 份是什么?它正好是 原来 A 的量! (b = 5)
“剧透”: Bingo!我们成功地把“原 A 的量”放进了杯子 B!

第三步:a = a b;

想法: 现在的 A 还是那个“总量”(8 份),但 B 已经变成了“原 A 的量”(5 份)。如果我从 A 的总量里,减去 B 现在的值(也就是“原 A 的量”),那剩下的不就是“B 的量”了吗?
操作: 我用杯子 A 里的 8 份混合物,减去杯子 B 现在拿到的那 5 份(也就是原来的 A)。
结果:
杯子 A 现在得到了 8 5 = 3 份。这 3 份是什么?它正好是 原来 B 的量! (a = 3)
杯子 B 还是 5 份。 (b = 5)

看! 最终 A 变成了 3 (原 B 的值),B 变成了 5 (原 A 的值)。就像我们用魔术一样,在不额外拿出杯子的情况下,完成了交换!

这背后藏着什么“学问”?

这个技巧,本质上是利用了加法和减法的逆运算。

`a = a + b`:我们创造了一个新的数值,这个数值是 `a` 和 `b` 的“和”。
`b = a b`:当我们从这个“和”里减去 `b` 的原始值时,得到的就是 `a` 的原始值。
`a = a b`:当我们从这个“和”里减去 `a` 的原始值(它现在存储在 `b` 里)时,得到的就是 `b` 的原始值。

整个过程,就好比你有两个装着不同东西的盒子,你把它们的内容都倒进一个大桶里,然后从中“舀出”你原来某个盒子的量,再把剩下的“挤压”成另一个盒子的量。

它的“兄弟”:异或交换

其实,还有一个非常相似的技巧,是用异或(XOR)运算来实现的。异或运算有一个特别的性质:

1. `x ^ x = 0` (任何数自己异或等于 0)
2. `x ^ 0 = x` (任何数和 0 异或等于它本身)
3. `x ^ y = y ^ x` (异或满足交换律)
4. `(x ^ y) ^ z = x ^ (y ^ z)` (异或满足结合律)

最关键的是,异或运算本身就是自己可逆的!也就是说,`a ^ b ^ b` 最终会等于 `a`。

所以,异或交换是这样的:

```
a = a ^ b
b = a ^ b // 这里的 a 是 (原 a ^ 原 b),所以 b 变成 (原 a ^ 原 b) ^ 原 b = 原 a
a = a ^ b // 这里的 a 还是 (原 a ^ 原 b),而 b 已经变成了 原 a,所以 a 变成 (原 a ^ 原 b) ^ 原 a = 原 b
```

这个异或交换,在某些底层操作或者特定场合,可能比加减法交换效率更高,因为异或通常是 CPU 可以直接执行的位运算,不涉及数值溢出的风险(虽然在大多数编程语言中,整数溢出会自动处理,但底层原理不同)。

什么时候用?为什么要用?

说实话,在现代编程里,不推荐直接使用这种“不借助临时变量的交换”。原因很简单:

1. 可读性差: 别人(或者未来的你)看到 `a = a + b; b = a b; a = a b;` 会一脸懵逼,需要花时间去理解它到底在做什么。而 `temp = a; a = b; b = temp;` 或者更现代的 `a, b = b, a` (Python 等语言支持的元组赋值) 一眼就能看懂。
2. 潜在风险: 对于加减法交换,如果 `a` 和 `b` 的和超过了变量类型所能表示的最大值,就会发生溢出,导致结果错误。虽然很多时候程序会处理,但这增加了不确定性。异或交换就没有这个问题,因为它是位运算。
3. 现代优化: 现在的编译器非常智能。即使你写了 `temp = a; a = b; b = temp;`,编译器也很可能把它优化得和直接交换一样高效,甚至通过寄存器分配等手段,根本就不需要实际的内存“临时变量”。

那么,为什么还要知道它呢?

面试题/智力题: 这是计算机科学教育中常常出现的“面试题”或者“趣味题”,用来考察你对基本运算原理的理解,以及解决问题的创意。
理解底层: 了解这种技巧,能帮助你更深入地理解计算机底层是如何进行数据操作的。
特定场景: 在极少数对内存占用要求极其苛刻,并且有把握处理好溢出问题的嵌入式开发或者底层系统编程中,可能会有人用到。但即便如此,也很少见。

总结一下

你提到的 `a=a+b;b=ab;a=ab;` 是一种利用加减法逆运算实现变量值交换的巧妙技巧,它避免了使用额外的临时变量。它的核心思想是:

1. 通过相加得到一个包含两者信息的“和”。
2. 从“和”中减去其中一个原始值,得到另一个原始值。
3. 再从“和”中减去刚才得到的那个原始值,从而得到第一个原始值。

它就像一种“数字魔术”,背后是算术的逻辑。同时,它也有一个“异或兄弟”,用的是位运算的特性,在某些方面更可靠。

虽然在日常编程中我们很少直接使用它,因为它影响可读性和安全性,但了解这种技巧,绝对是丰富你的编程知识库,让你在遇到某些“刁钻”问题时,能多一个思路,甚至在和别人交流时,能说出:“嘿,我知道那个不用临时变量就能交换值的‘小把戏’!”

希望我这么讲,让你觉得更生动,更像是在跟朋友聊技术,而不是在读一篇生硬的教程。

网友意见

user avatar

既然这种“算法”好,不看汇编肯定是没有说服力的。以下是多种C语言交换变量的汇编:

一、传统的临时变量法

tmp = a;

a = b;

b = tmp;

把上述代码编译:

mov eax,dword ptr [a]

mov dword ptr [tmp],eax

mov eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [tmp]

mov dword ptr [b],eax

6个mov指令,很直观,用eax寄存器做倒手,完成三次变量赋值操作,每次赋值是2个mov指令。效果中规中矩,何乐而不为?

二、加减法:

a = a+b;

b = a-b;

a = a-b;

汇编:

mov eax,dword ptr [a]

add eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [a]

sub eax,dword ptr [b]

mov dword ptr [b],eax

mov eax,dword ptr [a]

sub eax,dword ptr [b]

mov dword ptr [a],eax

6个mov+1个add+2个sub,一共9个指令!你可能会说,为什么会这样?C语言里同样是3个语句,为啥临时变量法和加减法会产生不同的汇编指令数量?这是因为加减法需要先将操作数复制到寄存器(eax)里,再做加减法运算。事实证明这样做除了节省一个整数的空间外,在效率和可读性上远比临时变量法差。平常千万不要用。

三、异或法:

a = a^b;

b = a^b;

a = a^b;

汇编:

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [b],eax

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [a],eax

同样是9个汇编指令,和加减法一样,需要把变量移入eax再做计算。考虑到异或运算快于加减运算,所以只比加减法好一丁点。这方法也非常糟糕,平常千万不要用。

四、内联汇编版

有没有既不需要内存变量又高效的方法呢,有!那就是内联汇编

__asm{

mov eax,a

xchg eax,b

mov a,eax

}

将内联汇编代码插入__asm{}块内,简简单单三句指令,第一句将a复制入寄存器eax内,第二句将eax和b交换,第三句将eax复制入a内。这样做的优点就是充分发挥了汇编的高效,不需要额外内存变量做临时变量。缺点是代码可读性差,不懂汇编的读着会懵逼。所以,该不该这样用,还得看实际情况

——————————

根据评论区给出的建议,已将原来的“反汇编”改为“汇编”和“编译”

虽然我的汇编代码是用VS的反汇编查看的,但这里用“编译”才是这种优化方法的思想本质。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有