逻辑运算和位运算——异或

##逻辑运算和位运算——异或

如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。

更通俗的讲就是:两个值不同为真,两个值相同为假。

10
10(假)1(真)
01(真)0(假)
异或运算真值表

上面是异或在逻辑运算中的使用。但如果异或放在位运算中,就有很多的应用场景。


我们把上面的表抽取出来,看成位运算的话可以得到下面两个表,并得出两个简单结论:

10
010
用0进行按位异或运算

结论一:如果一个数用0进行按位异或的话,会得到这个数本身。

10
101
用1进行按位异或运算

结论二:如果一个数用1进行按位异或的话,会得到相反的数。也就是取反。


用上面的两个结论,我们有四种基本的异或使用环境:

使用环境一:使某些特定的位取反。

a = 10100001;
b = 00000110;
c = a ^ b; // 0b10100111

使用环境二:实现两个值的交换,而不必使用临时变量。

a = a ^ b; // a=0b10100111
b = b ^ a; // b=0b10100001
a = a ^ b; // a=0b00000110

使用环境三:将变量自身置零。

a = 10100001;
a = a ^ a; // 0b00000000

使用环境四:快速判断两个值是否相等。

if ((a ^ b) == 0)
{
	//如果a和b相同则继续执行
	...
}

从上面的几个基本使用方法还可以衍生出一些高级的用法。

用法一:如异或的加密运算。

#include <stdio.h>

int main()
{
	char a = 'w'; // 原文
	char secret = '8'; // 密钥

	a = (char)(a ^ secret); // 加密
	printf("%c\n", a); // 大写字母O
	a = (char)(a ^ secret); // 解密
	printf("%c\n", a); // w
}

这里使用了异或取反的特点。用同一个密钥(在上面代码中为变量secret),对相同的位进行异或取反两次就可以得到原文。也就是负负得正的原理,“负”一次为密文,再“负”一次就是原文了。

用法二:检测两个数不同的比特位位置。

int a = 0b10100001;
int b = 0b10101011;
int c = a ^ b; // 0b00001010

根据结果我们可以看到数a和数b,第1位和第4位的比特位是不同的。当且仅当只有一个数的某位上为1时,结果的该位才为1。否则结果的该位为0。

用法三:数a需要改变多少个比特位才能得到数b。

这是上面用法的扩展。

#include <stdio.h>

int main()
{
	int a = 0b10100001;
	int b = 0b10101011;
	int c = a ^ b; // 0b00001010

	int count = 0;
	while (c)
	{
		c &= (c - 1);
		count++;
	}
	printf("%d", count); // count的值为2
}

在上面代码中,数a和数b异或的结果c为0b00001010 。结果c表示,数a需要根据数c,在数c比特位为1的对应位置取反,就可以得到数b。具体到上面的代码中,数a需要在第1位和第4位的位置取反,就可以得到数b。而代码中while循环和其中count的意思是,数a需要改变2个比特位才能得到数b。