位运算可以直接操作计算机内存中的整数的二进制位,位运算包括按位取反,按位与,按位或,按位异或,按位左移,按位右移六种运算。计算机在运算的时候是以补码进行运算的,所以运算会把符号也一并运算。
按位取反(~a) 把一个数的二进制位取反,0变成1,1变成0,如果是有符号的数据符号也会一并取反
按位与(a & b) 按位相与。包括符号。按位与的应用:
1 判断奇数偶数:奇数和1与1,偶数和1与是0
按位或(a | b) 按位或的常见应用是二进制特定位上的无条件赋值,例如要把一个数的末尾变成1,就是与1按位或,如果要把一个数的末尾变成0就与1按位或之后在加一。就是将这个数变成和该数最相近的偶数。
按位左移:左移一位相当于乘以2
右移一位相当于除以2。
位运算的一些应用:
1 二进制中1有偶数个还是奇数个
这样a & (a - 1)相当于把a的最后一位1抵消了。
如此往复,当抵消到最后一位1时,a & a - 1 = 0。所以a & a - 1可以消除a的最右边的1。
2 二分查找32位整数的前导0个数
第一种简单粗暴的解法:
按位取反(~a) 把一个数的二进制位取反,0变成1,1变成0,如果是有符号的数据符号也会一并取反
按位与(a & b) 按位相与。包括符号。按位与的应用:
1 判断奇数偶数:奇数和1与1,偶数和1与是0
按位或(a | b) 按位或的常见应用是二进制特定位上的无条件赋值,例如要把一个数的末尾变成1,就是与1按位或,如果要把一个数的末尾变成0就与1按位或之后在加一。就是将这个数变成和该数最相近的偶数。
按位异或(a ^ b) 这个运算可以用来加密,因为(a ^ b) ^ b = a。因此也可以用来交换两个数:
int main (int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
与1异或可以翻转特定位的值,与0异或可以保留原值。按位左移:左移一位相当于乘以2
右移一位相当于除以2。
位运算的一些应用:
1 二进制中1有偶数个还是奇数个
简单粗暴的做法:
#include <iostream>
#include <stdio.h>
using namespace std;
int main() {
int a = 1314520;
int b = 1;
int c = 0;
for (int i = 0;i < 32;i++) {
if (a & b) {
c++;
}
b <<= 1;
}
cout << c <<endl;
}
但是这样纯粹就是靠计数,通过x - 1将x的二进制最后一位变成0。代码如下:#include <iostream>
#include <stdio.h>
using namespace std;
int main() {
int a = 1314520;
int c = 0;
while (a) {
c++;
a &= (a - 1);
}
cout << c << endl;
}
但是为什么这样呢?我们可以假设a的最后一位为1,那么a - 1的最后一位为0,相与之后a的最后一位为0。假设a的二进制的最后一个1是第m位,假设a一共有n位那么那么a - 1 = a[n ... m + 1] 0 11 ... 1这样a & (a - 1)相当于把a的最后一位1抵消了。
如此往复,当抵消到最后一位1时,a & a - 1 = 0。所以a & a - 1可以消除a的最右边的1。
2 二分查找32位整数的前导0个数
第一种简单粗暴的解法:
#include <iostream>
#include <stdio.h>
using namespace std;
int main() {
int a = 1;
int i = 0;
for (i = 0;i < 31;i++) {
a = a >> 1;
if (a == 0) {
break;
}
}
cout << 31 - i << endl;
}
向右移位是判断有几位非0。因此可以使用二分法
#include <iostream>
#include <stdio.h>
using namespace std;
int main() {
int a = 4;
int c = 0;
int m = 16;
int cnt = 16;
while (m < 31) {
if (a >> m == 0) {
c += cnt;
a <<= cnt;
}
cnt /= 2;
m += cnt;
}
c -= (a >> 31);
cout << c << endl;
}