2020-11-20 逻辑运算(异或为什么可以交换值)
前言
在写冒泡排序的时候,同事看了我的代码,跟我说,你这个数据交换,不需要中转站的,用一个异或操作就可以啦。具体代码是:
a = 2;
b = 3;
// 数据交换(未使用异或)
temp = a;
a = b;
b = temp;
// 数据交换(使用异或)
a = a ^ b;
b = a ^ b;
a = a ^ b;
// a = 3, b = 2;
怎么样,乍一看是不是有点惊奇,别着急,让我们一探究竟。
逻辑门
- 与门:全为1才出1
- 或门:有1就出1
- 非门:全为0才出1
- 同或门:相同就出1
- 异或门:不同就出1
- 与非门:先与再非(全为1就出0)
- 或非门:先或再非(有1就出0)
这八种逻辑门在数字电路中非常重要,但是我们程序员只需要了解即可。
逻辑运算
- 逻辑与(&&):
运算符左侧为真值则继续往后运算,运算符左侧为假值,则返回运算符左侧值。1 && 2 && 3 // 3 1 && 0 && 2 // 0 0 && 2 // 0
- 与运算(&):
将运算符左右值转化为二进制数进行逻辑门运算,true转为1,false转为0,true与奇数得1,false与任何数都得03 & 2 // 3 true & 1 // 1 true & 2 // 0
- 逻辑或(||):
向右查找到第一个真值,并返回。0 || 3 || 4 // 3 1 || 2 || 3 // 1
- 或运算(|):
和与运算相同,先转化为二进制再进行逻辑门运算。0 | 1 // 1 3 | 4 // 7
- 非运算(!):
非运算就是取值的“反”,真的变成假的,假的变成真的,只输出true/false,可叠加运算!1 // false !0 // true !!!1 // false
- 异或运算(^):
将值转化为二进制,进行异或门运算
发现规律了吗?通过异或,我们可以利用值本身,将两个值进行交换。2 ^ 3 // 1 1 ^ 3 // 2 1 ^ 2 // 3
为什么是异或
二进制数只有两个值,不是0就是1,那么重点来了:
两个数不同就表明他们二进制表示中有一位或者多位不同,如10和12:
10的二进制:1010
12的二进制:1100
一对比,发现这两个数的中间两位数是不同的。那异或运算特点是什么?不同就出1。那说明两个数进行异或以后,他们不同的位置的数就会变成1。
10 ^ 12 // 0110
很明显,中间两位是1,代表10和12的二进制表示,中间两位数是不同的。与上面我们推断出的规律是一样的。
交换位置
两个不同的数想要交换,而我们已经知道了两个数的二进制表示中,哪些位的值是不同的,所以只需要将不同位的数交换即可。
第一次异或
a = 10; // 1010
b = 12; // 1100
// 第一次
a = a ^ b; // 0110
此时a的值为0110,b还是原来的值12(1100)。a是代表了原来a和b的差异值(中间两位不同);
第二次异或
b = a(差异值) ^ b(初始b); // 1010
此时b的值是差异值a和原来b值异或的结果。
注意观察,异或是找不同,可以得出:
已知左边一位数为0,那右边对应位置的数一定是其本身(0代表相同,出0,1代表不同,出1)
已知左边一位数为1,那右边对应位置的数一定取反(0代表不同,出1,1代表相同,出0)
那说明此时b是原来的b值将二进制表示的中间两位进行取反的结果。还记得第一次异或得出了什么结论吗?a与b再二进制表示中 中间两位 不同,
既然b已经进行了差异位的取反,那不就变成了原来a的值了吗?--仔细想想
第三次异或
a = a(差异值) ^ b(初始a); // 1010将中间两位取反,得出1100
第二次异或结束后,差异值0110,中间两位为1,边上两位为0,利用上面的结论,在第三次异或中,得出a的值为b(原来的a)进行差异取反,也就得到了原来的b的值。
结论
异或交换的核心思想就是:
- 第一次异或得到差异值,将其随意赋给一个值a;
- 第二次异或通过差异取反(差异值a ^ b)可以得到a的原始值,将其赋给b;
- 第三次异或还是通过差异取反(差异值a ^ b(原始值a))得到原始值b,将其赋给a;
废话
不由感叹数学果然让人头秃,誓要成为数学亡子 [滑稽]。
周五就该下棋,九五至尊不香吗?