位运算
二进制的位运算并不是很难掌握,因为位运算总共只有5种运算:与,或,异或,左移和右移。
左移运算符m《n表示把m左移n位。左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0。比如:
00001010《2=00101000
10001010《3=01010000
右移运算符m》n表示把m右移n位。右移n位的时候,最右边的n位将被丢弃。但右移时处理最左边位的情形要稍微复杂一点。如果数字是一个无符号数值,则用0填补最左边的n位。如果数字是一个有符号数值,则用数字的符号位填补最左边的n位。也就是说如果数字原先是一个正数,则右移之后在最左边补n个0;如果数字原先是负值,则右移之后在最左边补n个1.下面是对两个8位有符号数作右移的例子:
00001010》2=00000010
10001010》3=11110001
题目:
请实现一个函数,输入一个整数,输出该数二进制中1的个数。例如,把9表示成二进制1001,有2位是1.因此如果输入9,该函数输出2.
可能引起循环的解法
思路:
首先判断整数二进制表示中最右边一位是不是1。接着把输入的整数右移一位,此时原来处于从右边数起的第二位被移到最右边了,再判断是不是1。这样每次移动一位,直到整个整数变成0为止。
现在的问题变成怎么判断一个整数的最右边是不是1了。这很简单,只要把整数和1做位运算看结果是不是0就知道了。
1除了最右边的一位之外所有位都是0。如果一个整数与1做与运算的结果是1,表示该整数最右边一位是1,否则是0。基于这个思路,很快写出代码:
int NumberOf1(int n)
{
int count=0;
while(n)
{
if(n&1)
count++;
n=n>>1;
}
return count;
}
问题1:把整数右移一位和把整数除以2在数学上是等价的,那上面的代码中可以把右移运算换成除以2吗?
答案是否定的,因为除法的效率比移位运算要低得多,在实际编程中应尽可能地用移位运算符代替乘除法。
问题2:上面的函数如果输入一个负数,比如0x80000000,运行的时候会发生什么情况?把负数0x80000000右移一位的时候,并不是简单地把最高位的1移到第二位变成0x40000000,而是0xC0000000。这是因为移位前是个负数,仍然要保证移位后是个负数,因此移位后的最高位会设为1。如果一直做右移运算,最终这个数字就会变成0xFFFFFFFF而陷入死循环。
常规解法:
为了避免死循环,我们可以不右移输入的数字n。首先把n和1做与运算,判断n的最低位是不是为1。接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1……这样反复左移,每次都能判断n的其中一位是不是1.基于这种思路,代码修改如下:
int NumberOf1(int n)
{
int count=0;
unsigned int flag=1;
while(flag)
{
if(n&flag)
count++;
flag=flag<<1;
}
return count;
}