先给大家讲个笑话吧,说世界上有10种人,一种知道二进制,而另一种不知道二进制。
位运算的基础就是二进制。
二进制的位运算包括五种运算:
与,或,异或,左移,右移
与(&) 0&0 = 0,0&1 = 0;1&0 = 0;1&1 = 1;
或(|) 0|0 = 0, 0|1 = 1; 1|0 = 1; 1|1 = 1;
异或(^)0^0 =0; 0^1 = 1; 1^0 = 1; 1^1 = 0;
左移(<<) 00001001 << 2 =00100100
右移(>>) 10001010 >> 3 = 00010001
前面已经提到过,右移分为算术右移和逻辑右移,主要在于无符号数和有符号数的区别。
逻辑右移:右移后,左端补零
00001010 >> 2 = 00000010
算术右移:右移后,左端补最高位数
有符号数10001010 >>3 = 11110001
对于无符号数按照逻辑右移,有符号数一般为算术右移
1.位运算的特点
1,按位与(&)
清零特定位,10001010 & 01111111 = 00001010 (首位清零)
取指定位, 10001010 & 10000000 = 10000000 (取首位)
2,按位或(|)
将某一位置置一,10001010 | 00000001 = 10001011 (末尾置一)
3,位异或(^)
使特定位取反 ,10001010 ^ 10001010 = 00001010 (首位取反)
不引入第三变量,交换两个变量的值
a = a^b ;b = a^b;a = a^b
4,补码运算
-x = ~x +1
2.位运算的应用
应用举例:
1,判断int型变量a 是奇数还是偶数
a & 1 = 0 偶数(末尾为零)
a & 1 = 1 奇数
2,取变量的第k位 a>> k & 1
3,将变量的第k位清零,a = a& ~(1<< k)
4,将变量的第k 位置1,a = a |(1<< k)
5,将变量循环左移k次,就是从左移出的从右端进,a = a << k | a >>16-k 设(sizeof(int) = 16);
6,整数的平均值
对于两个整数x,y ,如果用(x + y)/2求平均值,会产生溢出,因为x+y可能超出范围。
int average(int x,int y)
{
return (x&y) +((x^y) >> 1);
}
7,乘法转换为位运算
a *(2^n) = a << n
8,除法运算转换为位运算
a/(2^n) = a>> n
3.由计算绝对值引出补码,原码,反码,有符号数的讨论
int abs(int x)
{
int y;
y = x>> 31; (若x为负数,1111111111111111)有符号数为-1
return (x^y)-y; //
假如x= -2;(1111111111111110)
y= 1111111111111111
x^y = 0000000000000001
x^y-y = 0000000000000010 表示+2
如果求原码:如果是求原码,符号位不变,其他位按位取反,再加一
~x=1000000000000001
~x+1 = 1000000000000010
原码:符号+值,表示-2。
上述求绝对值方法和求原码的方法类似,相当于是把原码的符号位从1变为零,如果是正数,则不变,这一些都是由x>>31,获得符号位开始。正数的补码=原码=绝对值
注意:原码和补码只是有符号数的两种不同的表示方式。
求补码:
若x < 0,x = x+2^w; 若x> 0,x = x
x = -1,补码为(1111111111111111),无符号数表示的 Umax和补码表示的-1有相同的位模式。
4.c与指针第五章编程练习三
题目:请编写函数
unsigned int reverse_bit(unsigned int value);
这个函数的返回值是把value的二进制模式从左到右变换一下后的值,例如在32位机器上,25这个值包含下列各个位:
00000000000000000000000000011001
函数的返回值是2550136832,它的二进制模式是:
10011000000000000000000000000000
编写函数时要注意不要让它依赖于你的机器上整型值的长度。
unsigned int reverse_bit(unsigned int value)
{
unsigned int answer;
unsigned int i;
answer = 0;
for(i = 1;i != 0;i <<=1) //通过是1一直左移到越过最高位,不依赖机器长度
{
answer << 1; //左移挪出最右端位置
if(value & 1 != 0) //如果value最后一位为1
answer |= 1; //就把这个1放在answer的最右端
value >> 1; //value已经被挪走的数就丢弃掉
}
return answer;
}
//利用的是从value右端出数,然后从answer右端进数,然后再左移,就将二进制位模式顺序改变。
5.剑指offer 面试题10 求二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数,例如把9表示成二进制是1001,有2位1
常规思路:将整数右移一次,然后&1,判断最后一位是否为1,为1,则计数加1.
代码如下:
int NumberOf1(int n)
{
int count =0;
while(n)
{
if(n & 1 )
count ++;
n >>=1;
}
return count;
}
看起来似乎很合理,但是如果n为负数,作为有符号数,右移将在左端补1,一直右移,将陷入死循环。
为了避免死循环,我们可以不右移输入的数字n。
int NumberOf1(int n)
{
int count = 0;
unsigned int flag = 1;
while(n)
{
if(n & flag)
count++;
flag << = 1;
}
return count;
}
32位的整数需要循环32次,效率不高
方案三:
int NumberOf1(int n)
{
int count = 0;
while(n)
{
++count;
n = (n-1) & n;
}
return count;
}
关键点就是把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于是把整数的二进制表示中的最右边一个1变成0.