一.C语言中异或概述
在C语言中,异或(XOR)是一种二进制运算,它对两个数字的对应位进行比较,如果这两个位不同,则结果为1;如果这两个位相同,则结果为0。异或运算符在C语言中是^。
1.异或运算有几个有趣的性质:
交换律:a ^ b = b ^ a
结合律:(a ^ b) ^ c = a ^ (b ^ c)
任何数与0异或都等于其本身:a ^ 0 = a
任何数与自身异或都等于0:a ^ a = 0
异或运算对同一个数进行两次,结果仍为原数:a ^ b ^ b = a(因为b ^ b = 0,然后a ^ 0 = a)
二.异或的应用
1.交换两个变量的值
异或运算可以用来交换两个变量的值,而不需要使用临时变量。这是通过异或运算的性质实现的:任何数与自身异或结果为0,任何数与0异或结果为其自身,以及异或运算的交换律和结合律。
#include <stdio.h>
int main() {
int a = 5, b = 10;
a = a ^ b;
b = a ^ b; // 此时b为原a的值
a = a ^ b; // 此时a为原b的值
printf("a = %d, b = %d\n", a, b); // 输出: a = 10, b = 5
return 0;
}
2.奇偶性判断
一个数与1进行异或运算,可以用来判断该数的奇偶性。如果结果为0,则该数为偶数;如果结果为1,则该数为奇数。
#include <stdio.h>
int main() {
int a = 12345;
if ((a ^ 1) - a == 1) {
printf("%d 是偶数\n", a);
} else {
printf("%d 是奇数\n", a);
}
// 输出: 12345 是奇数
return 0;
}
3.加密与解密
异或运算可以用于简单的数据加密和解密。由于异或运算的自反性,使用相同的密钥进行两次异或操作可以恢复原始数据。
#include <stdio.h>
int main() {
unsigned char message = 0x89AB;
unsigned char key = 0xA5A5;
unsigned char secret = message ^ key; // 加密
unsigned char decrypted = secret ^ key; // 解密
printf("加密后: %X\n", secret);
printf("解密后: %X\n", decrypted); // 输出应与原始message相同
return 0;
}
4. 查找出现奇数次的数字
在数组中,如果只有一个数字出现了奇数次,其他数字都出现了偶数次,可以使用异或运算来找到这个出现奇数次的数字。因为任何数与自身异或结果为0,所以出现偶数次的数字在异或运算中会相互抵消,最后剩下的就是出现奇数次的数字。
#include <stdio.h>
int findOddNumber(int arr[], int len) {
int result = 0;
for (int i = 0; i < len; i++) {
result ^= arr[i];
}
return result;
}
int main() {
int arr[] = {1, 2, 3, 2, 1, 4, 4};
int len = sizeof(arr) / sizeof(arr[0]);
int oddNumber = findOddNumber(arr, len);
printf("出现奇数次的数字是: %d\n", oddNumber);
return 0;
}
5.两个值的快速比较
例如比较两个值是否相等时,一般我们使用这个 a==b,如果两个数相等 ,a ^ b 的结果为零。
if((a^b) == 0) {
//a和b若相同则为true
}
6.数据备份
使用异或也可以很容易实现多个数据的互相备份,假如有数据a、b、c,则d = a ^ b ^ c,然后把数据d备份下来。
当a丢失时,可使用d ^ b ^ c来恢复
当b丢失时,可使用d ^ a ^ c来恢复
当c丢失时,可使用d ^ a ^ b来恢复
由此可见备份了一份数据d后,丢失了其他任何一个数据,都可以通过备份数据与其它数据异或恢复回来,磁盘阵列技术raid5的数据备份原理,用的就是这个特性。
7.奇偶校验
首先先看一个例子理解下:判断一个二进制数中1的数量是奇数还是偶数。
求10110101中出现1的数量是奇数还是偶数;可将10110101逐位异或操作,结果为1就是奇数个1,结果为0就是偶数个1。
奇校验:原始码+1位校验位,总共有奇数个1;
偶校验:原始码+1位校验位,总共有偶数个1。
比如为原始码添加奇偶校验位:
原始码:1101010
判断1的个数是否为偶数:1^1^0^1^0^1^0=0,因为逐位异或的结果为0,所以1的个数为偶数,奇校验则在原数据末尾添1,变成11010101;偶校验则在原数据末尾添0,变成11010100。
8.对某些特定的位翻转
由于不管是0或者1与1做异或操作后将得到原值的相反值,因此当我们需要翻转一个整数的某些位时,我们可以使用位异或运算来实现掩码操作,将对应的数据位进行翻转。
//定义一个整数n
int a = 0x0f; //二进制表示为00001111
//如果需要翻转a的第4和第5位,则定义掩码mask
int mask = 0x18; //二进制表示为00011000
//使用位异或运算翻转n的第3位和第4位
n = n ^ mask; //结果为二进制表示为00010111
在单片机编程中对GPIO输出控制LED的闪烁原理也是如此,定义了一个宏,直接通过GPIOA口对ODR寄存器进行操作,异或PIN2的位。实现引脚PA2输出高低电平的翻转控制LED闪烁。
// PA2引脚输出电平翻转
#define LED_TOGGLE() GPIOA->ODR ^= GPIO_PIN_2
9.例如判断两个int32类型的数据的符号是否相同,其中31是符号位的偏移量:
if (((a ^ b) >> 31) & 1) {
"符号不相同!";
} else {
"符号相同!";
}