位操作符 & | ^ 的妙用(持续更新中)

位操作符是针对二进制位进行计算的,他们的操作数必须是整数

目录

例1:交换两个变量(不创建临时变量)

例2:统计二进制中1的个数

法1:得到二进制的每一位

法2:右移>>   与&1

法3:n & ( n - 1 )

例3:求两个数二进制中不同位的个数(32)

法1:右移>>   与&1

法2:异或 ^

例4.交换奇偶位(用宏)

如何拿出所有奇/偶位?

例5.找单身狗

版本1:

版本2:升级

法1:遍例

法2:分组


例1:交换两个变量(不创建临时变量)

我们首先要了解异或 ^ 的3个特点:1.  a ^ a = 0     2.  0 ^ a = a    3.  异或支持交换律

代码这样写:

#include <stdio.h>
int main()
{
	int a = 10;
    int b = 20;
    printf("交换前:a = %d b = %d\n", a,b);
    a = a^b;//(1)
    b = a^b;//(2)
    a = a^b;//(3)
    printf("交换后:a = %d b = %d\n", a,b);
	return 0;
}

(2):b = (a^b)^b = a^(b^b) = a^0 = a      (3):a = (a^b)^b = (a^b)^a = 0^b = b   就完成了交换



例2:统计二进制中1的个数

主函数如下,各个方法只展示调用函数部分

int main()
{
	int n = 0;
	scanf("%d", &n);

	int ret = NumberOf1(n);
	printf("%d\n", ret);

	return 0;
}

法1:得到二进制的每一位

我们将它与如何得到十进制作对照:

int NumberOf1(unsigned int n) 
{
	int count = 0;
	while (n)
	{
		if (n % 2 == 1)
			count++;
		n = n / 2;
	}
	return count;
}

法2:右移>>   与&1

注意到 与&:对应二进制位有0,则为0;同时为1才是1

我们把这个数右移>> 32次,每次&1的结果进行判断,结果为1,计数

int NumberOf1(int n)
{
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		if (((n >> i) & 1) == 1)
		{
			count++;
		}
	}
	return count;
}

法3:n & ( n - 1 )

     与1次,计数,直至为0

int NumberOf1(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}

你可能会有疑问:要是n为负数,循环怎么停止?
解答:假设一个负数
           n                    1100 0000 0000 0000 0000 0000 0000 0000 
           n-1                     1011    1111    1111   1111   1111   1111    1111   1111 
           n & n - 1            1000 0000 0000 0000 0000 0000 0000 0000 (此时为负数最小值)
           继续进行
           n                    1000 0000 0000 0000 0000 0000 0000 0000 
           n-1                     0111    1111    1111   1111   1111   1111    1111   1111 
           n & n - 1            0000 0000 0000 0000 0000 0000 0000 0000 
           这个时候就是0了

疑问:那倒数第三行的n-1,符号位为0,不就是正数了吗
解答:对呀,因为此时n已经是当前32位能表示的最小值了,-1一定是超过了这个范围的,就会循环,成为正数最大值,然后&的时候就没了



例3:求两个数二进制中不同位的个数(32)

主函数部分:

int main()
{
	int m = 0;
	int n = 0;
	scanf("%d %d", &m, &n);
	int ret = count_diff_one(m ,n);
	printf("%d\n", ret);
	return 0;
}

法1:右移>>   与&1

这个和例2,法2相似

int count_diff_one(int m, int n)
{
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		if (((m >> i) & 1) != ((n >> i) & 1))
		{
			count++;
		}
	}
	return count;
}

法2:异或 ^

思路:

  1. 先将m和n进行按位异或,此时m和n相同的二进制比特位为0,不同的为1
  2. 统计异或后的二进制中,有多少个1(回到了例2)
int count_diff_one(int m, int n)
{
	int count = 0;
	int tmp = m ^ n;
	//统计tmp的二进制中有几个1
	while (tmp)
	{
		tmp = tmp & (tmp - 1);
		count++;
	}
	return count;
}


例4.交换奇偶位(用宏)

写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。交换第1 2,3 4,5 6......31 32位

思路:拿出所有奇数位,左移1;拿出所有偶数位,右移1。结果再相加

如何拿出所有奇/偶位?

eg:1 1 1 1 1 1 1 1 奇数位偶数位

拿出奇数位:
1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1   (16进制:0x55)与 & :0 1 0 1 0 1 0 1 —— (a&0x55555555)<<1

拿出偶数位:
1 1 1 1 1 1 1 1
1 0 1 0 1 0 1 0   (16进制:0xaa)与 & :1 0 1 0 1 0 1 0 —— (a&0xaaaaaaaa)>>1

#define SWAP(x) (x=(((x&0x55555555)<<1)+((x&0xaaaaaaaa)>>1)))

int main()
{
	int a = 10;
	SWAP(a);
	printf("%d\n", a);

	return 0;
}

例5.找单身狗

版本1:

有一个数组,其中只有一个数字出现1次,其余数字出现2次。编写一个函数找出这两个只出现一次的数字。
eg:1 2 3 4 5 1 2 3 4 
思路:把所有数字异或^在一起     a ^ a = 0  ,  0 ^ a = a 

版本2:升级

一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。编写一个函数找出这两个只出现一次的数字。
eg:1 2 3 4 5 1 2 3 4 6

法1:遍例

缺点:效率低,100个数字要遍例10000次。

int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
	int len = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int count = 1;

	for (i = 0; i < len ; i++)
	{
		count = 1;
		int j = 0;
		for (j = 0; j < len ; j++)
		{
			if (arr[i] == arr[j])
			{
				count++;
			}
		}
		if (count == 2)
		{
			printf("%d ", arr[i]);
		}
	}

法2:分组

我们想:能不能分组,各组都满足版本1。即:每组只有1个单身狗,剩下的都是成对出现的数字。
分组方法不同,分出每组的数据不同。

怎么分?以eg为例,找 5,6 的差异在哪

^ :二进制位,相同为0,相异为1
由于 5 != 6  ==>  5 ^ 6 != 0 ==>  ^ 的结果里一定有1
5:1 0 1
6:1 1 0
^ :0 1 1

总结:两个二进制数,^ 结果为1 的二进制位,记为 pos 。两个二进制数的 pos 位一定不相同
以 ^ 结果为1的二进制位数进行分组      ^ 结果为0的不管


eg中,以最低位为1的放到一组:5 1 1 3 3
                             0                  :6 2 2 4 4
eg中,以第二位为1的放到一组:6 2 2 3 3
                             0                  :5 1 1 4 4 (5,6 必然在2个组里)

以上2种分组,各组都满足版本1的形式。再将2组中,每组 ^ ,便可得到2个单身狗


//函数不能同时返回2个值,所以返回类型先写为void
void find_single_dog(int arr[], int sz, int single_dog[])
{

}

int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int single_dog[2] = { 0 };

	find_single_dog(arr, sz, single_dog);
	printf("%d %d\n", single_dog[0], single_dog[1]);
	return 0;
}

我们跳出以上分析,从头开始。
现在要把拿到数组里的2个单身狗 ^ 在一起,但我不知道谁是单身狗呀
所有数据都 ^ 在一起  <==>  2个单身狗 ^ 在一起 

看 ^ 结果,计算 ret 的二进制位里,哪一位第一次是1,记为 pos。2个单身狗的 pos 位一定不相等,再对每组 ^ 即可得到2个单身狗

怎么算?
例2  法2:右移>>  与&1   总共才32个二进制位,循环走起

void find_single_dog(int arr[], int sz, int single_dog[])
{
	int ret = 0;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		ret ^= arr[i];
	}
	//计算ret的二进制位里,哪一位是1
	int pos = 0;
	for (i = 0; i < 32; i++)
	{
		if (((ret >> 1) & 1) == 1)
		{
			pos = i;
			break;  //找到第一次出现1的二进制位了,不用再循环了
		}
	}
	//分组
	for (i = 0; i < 32; i++)
	{
		if ((arr[i] >> pos) & 1 == 1)
		{
			single_dog[0] ^= arr[i];
		}
		else
		{
			single_dog[1] ^= arr[i];
		}
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int single_dog[2] = { 0 };

	find_single_dog(arr, sz, single_dog);
	printf("%d %d\n", single_dog[0], single_dog[1]);
	return 0;
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章。