什么是位运算:
位运算是把数字用二进制表示之后,对每一位上0和1的运算。
二进制是指:把数字的每一位都是0或者1。比如十进制的2转换为十进制为10,而十进制的10转化为二进制为1010。
与、或、异或的运算规律:
左移运算符 m << n 表示把 m 左移 n 位。 在左移 n 位的时候,最左边的 n 位将会被丢弃,同时在最右边补上 n 个 0.
比如: 0000 1010 << 2 = 0010 1000
1000 1010 << 3 = 0101 0000
右移运算符 m >> n表示把 m 右移 n 位。在右移 n 位的时候,最左边的 n 位将会被丢弃,同时在最右边做这些处理:
如果这个数字是一个无符号数值,则用0填补最左边的 n 位。
如果这个数字是一个有符号数值,则用数字的符号填补最左边的 n 位。
比如: 0000 1010 >> 2 = 0000 0010
1000 1010 >> 3 = 1111 0001 // 第一位是1 ,是有符号数值,则补1.
二进制中1的个数:
输入一个整数,输出该数二进制表示中1的个数,例如,把9表示成二进制是1001,有2位是1,因此输入9,则输出2.
第一种方法:
先判断整数的二进制表示最右边一位是否为1,接着把输入的整数整体右移一位,再判断最后一位是不是1,直到整个整数变为0为止。
1的二进制表示为0111 1111,将这个整数和1进行位于运算,看结果是否为0,如果为1,则表示为该整数最右边一位为1,否则为0。
int Number1(int n)//可能引起死循环
{
int count=0;
while(n)
{
if(n&1) //和1进行位于运算
{
count++;
n=n>>1; //右移整数一位
}
}
return count;
}
可能会出现的问题:
(1)把整数右移一位和把整数除以2 在数学生等价的。但是不能把右移运算替换成除以2,因为除法的效率比移位运算低的多。
(2)可能会陷入死循环:如果输入一个负数,比如 0x8000 0000 ,则运行的时候,把他右移一位,并不是简单的把最高的一位移到第二位变成 0x4000 0000,而是0x C000 0000.因为移位前这是一个负数,仍然要保证移位后是一个负数,因此移位后的最高位会设为1.如果一直做右移运算,那么这个数字最终会变成 0xFFFF FFFF 而陷入死循环。
第二种:
为了避免死循环,我们可以不右移输入的数字n,首先把1和n 做与运算,判断n 的最低位是不是1.接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1.这样反复左移,每次判断n的其中一位是不是1.
如下图表示:
int Number2(int n) //不右移数字,而是一直左移1
{
int count=0;
unsigned int flag=1;
while(flag)
{
if(n & flag)
{
count++;
}
flag=flag<<1; // 把1左移一位得到2,再与整数做与运算
}
return count;
}
这种解法中,循环的次数就等于整数二进制的位数,32位的整数需要循环32次。
第三种:
把一个整数减去1,再和原来的整数做与运算,会把该整数最右边的1变成0,那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
int Number3(int n)
{
int count=0;
while(n)
{
++count;
n=(n-1)&n; //把整数减去1,再与原整数做位于运算,会把该整数最后边一个1变成0.
}
return count;
}