1. 二进制置1或置0
比如给一个int的第6位置1,可以用下面的方法
int main() {
int a = 20;
a |= 1 << (6 - 1);
printf("%d\n", a);
system("pause");
return 0;
}
置0的思路也是同样的原理,只不过更难想到一点
int main() {
int a = 20;
a &= ~(1 << (6 - 1));
printf("%d\n", a);
system("pause");
return 0;
}
2. 消去二进制中从右往左第一个1
让这个数按位与上自己减1
int main() {
int a = 20;
a &= (a - 1);
printf("%d\n", a);
system("pause");
return 0;
}
原理其实很简单,假如一个数从右往左第一个1是在第4位,即 1000,那么减去1就会变成0111,再按位与就消去了这个1,并且可以想到对这个1左边的所有位都没有影响。
这个技巧还可以做到很多事,比如数二进制中的1的个数之类的。
3. 判断一组数中是否有重复数字
例如有一个长度为5的数组,我希望里面的数字是1到5,顺序无所谓但不能有重复,那么怎么判断里面的数是否是1到5呢?
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int res = 0;
for (int i = 0; i < 5; i++) {
res |= 1 << (arr[i] - 1);
}
if (res == 0x1f) {
printf("Bingo!\n");
}
system("pause");
return 0;
}
我可以先取一个数res等于0,然后遍历数组中的每个数,用res或等上1左移每个数组元素的数值,相当于把res的对应二进制位置1,如果数组元素是1到5,那么res从右往左第1到5位就会被置1,所以结果一定是0x1f(0001 1111)。而如果数组元素有重复或者不是1到5,那么得数一定不等于0x1f,从而达到我们的目的。
4. 按位异或的奇特性质
按位异或做重要的性质就是:异或一个数两次等于没有异或过这个数。
所以一道很常见的面试题就是,一个数组中只有一个数出现了1次,其余所有数都出现了两次,找出这个数,我们在知道了异或的性质后就可以很快的解出这道题。
int main() {
int arr[] = { 1, 2, 2, 3, 3, 4, 4, 5, 5 };
int res = 0;
for (int i = 0; i < 9; i++) {
res ^= arr[i];
}
printf("%d\n", res);
system("pause");
return 0;
}
用这个奇特的性质还可以做到很多平常不能想象的事情。比如,交换两个变量这个最简单的问题,一般人第一时间想到的就是用一个中间变量tmp来实现交换。如果加一个条件不能使用中间变量,那么下面这个通过两个变量间的一系列数学变换达成目的的方法相信很多人也已经见过了。
int main() {
int a = -10, b = 20;
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
system("pause");
return 0;
}
但这个问题有一个不能忽视的问题,即两个int如果直接加在一起是有越界的风险的,所以这个解法还不够完美,那么还有没有办法把这个缺点也克服了呢?答案当然是肯定的,工具就是这篇博客的主角:位运算,具体点说就是按位异或。
int main() {
int a = -10, b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d\n", a, b);
system("pause");
return 0;
}
首先,a按位异或b赋值给了a。然后再把这个异或后的值异或b得到了a,赋值给了b,此时a是两个数异或后的值,b是a的值。最后再把a和b按位异或就得到了原来的b,赋值给a,目的达成!