1,左移运算如果处理负数的时候是咋样的呢???会不会通过左移将一个负数
变成正数???
2,左移右移的时候,它的位数有的限制吗???可以无限
大吗???
3,详解二进制中的1的个数
左移:
1,左移运算符m>> n表示把m左移n位。左移n位的时候,最左边的n位将
被丢弃,同时在最右边补上n个0
程序1:
#include
#include
using namespace std;
int main()
{
int a,c;
unsigned int b,d;
a= 5;
b= 5;
c= -5;
d= -5;
printf("a is :%d\n", a<<2);
printf("b is :%d\n", b<<2);
printf("\n c is :%d %d\n", c,c << 2);
printf("d is :%d %d\n", d , d <<2);
return 0;
}
运行结果:
可以看到:
1, int类型a= 5时,a左移两位后,变成了20
0000….. 0000 0101,左移两位后,变成了0000….. 0001 0100 (20)
2, unsignedint类型b= 5,b左移两位后,变成了20
0000….. 0000 0101,左移两位后,变成了0000….. 0001 0100 (20)
而这里,int和unsignedint的区别在于范围不一样:
int(32位):-2147483648~2147483647
unsignedint:0– 4294967295
3, int类型c= -5时,c左移两位后,变成了20
c的二进制表示为:1111….. 1111 1011,然后将其左移2位后,变
成了1111….. 1110 1100
(然后将其转化为源码形式:1000….. 0001 0100),所以结果为-20
4, 同上,unsignedint类型的d= -5, d左移两位后,变成了-20
但是,很想不明白,为什么,一个无符号的数(正数),输出后,会变
成了负数,这点很奇怪啊,
后来经过分析,明白了,其实这是输出的形式而已,因为我们是以“%d”来输
出的,在输出的过程中
就直接的将无符号的结果转化成了int,这样,我们可以来验证。
printf("d is :%u %u\n", d , d <<2);
运行结果:
可以看到:我们程序中的d(unsignedint类型)实际表示的是:4294967291,
左移两位后变成了4294967276(这回就可以正确理解了)
5,那么还是回到我们上面提到的问题一个负数会不会在左移的过程中变成
正数呢???哈哈,这个问题还
是那么有趣。。。
这样,我们继续来看程序:
#include
#include
using namespace std;
int main()
{
int a;
a= -5;
printf("\na is :%d %d\n", a,a << 5);
printf("\na is :%d %d\n", a,a << 29);
return 0;
}
运行结果:
可以看到,int类型的a在左移29位后,变成了一个正数
这是因为a的表示:1111 ….. 1011(总共32位),当我们把前面的29个1,全部移走之后,最高位
就变成了0,所以很自然的就变成了正数了
右移:
右移:m>> n(表示把m右移n位。右移n位的时候,最右边的n位将被丢弃。但右移时处理最
左边的情况要复杂点。如果数字是一个无符号数值,则用0填补最左边的n位。如果数字是一
个有符号数值,则用数字的符号位填补最左边的n位,也就是说:如果数字原先是一个正数
,则右移之后在最左边填补0,如果数字原先是负数,则右移之后在最左边填补1)
程序1:
#include
#include
using namespace std;
int main()
{
int a,b;
a= -5;
b= 5;
printf("a is :%d %d\n", a,a >> 2);
printf("b is :%d %d\n", b,b >> 2);
return 0;
}
运行结果:
int类型的-5右移两位为-2:
1111….. 1111 1011右移两位后(补1):1111….. 1110
所以,源码为:1000….. 0010(-2)
int类型的5右移两位为1:
0000….. 0000 0101右移两位后(补0):0000 ….. 0001(1)
关于移位的时候的移位的范围:
#include
#include
using namespace std;
int main()
{
unsigned int k,l, i,j;
i= 35;
l= 1 << i;
k= 1 << 35;
printf("l is :%d\n", l);
printf("k is :%d\n", k);
return 0;
}
出现了一个警告:left-move.cpp:12:11:warning: left shift count >= width of type
运行结果:
这是跟Intercpu的移位运算有关,至于两者的结果为什么不一样???
i= 35;l= 1 << i;在编译阶段,会被翻译成这样的汇编指令:
mov dword ptr[i],23h
mov eax,1
movecx,dword ptr[i]
shl eax,cl
mov dword ptr[j],eax
在shl一句中,eax=1,cl=35。而IntelCPU执行shl指令时,会先将cl与31进行and操作
,以限制左移的次数小于等于 31。因为35& 31 =3,所以这样的指令相当于将
1左移3位,结果是8。
而j=1<<35;一句是常数运算,编译器也会直接计算1<<35的结果。VC编译器发现35大
于31时,就会直接将结果设置为0。这行代码编译产生的机器指令是:
mov dword ptr[j],0
在C/C++语言中,移位操作不要超过界限,否则,结果是不可预期的。
下面是Intel文档中关于shl指令限制移位次数的说明:
The destination operand can be a register or a memory location.The countoperand can be an
immediate value or register CL. The count ismaskedto 5 bits, which limits the count range to
0 to 31. A specialopcode encodingis provided for a count of 1.
题解:二进制中1的个数
1,大家经常会想到的是:
先判断整数的二进制表示中最右边一位是不是1,接着把输入的整数右移一位,
此时原来处于从右边数起的第二位被移到最右边了,再判断是不是1.这样每次移动一位,直到整个
整数变成0为止。
if(n& 1)
count++;
n= n >> 1;
思考1:把整数右移1位和把整数除以2在数学上是等价的,那上面的代码中可以把右移
运算换成除以2吗???
当然不啊,,,因为除法的效率比移位中要低得多,在实际编程中应尽可能的
用移位运算符代替乘除法。
思考2:但是,如果我们这样判断的话,是会出错的,我们并不能保证,这个
整数一定是正数吗???
当然不可能啊,如果输入的是负数的那么是会进入一个死循环的,严重偏离题意的。
0X8000 00 00(32位),那么第一次的右移之后,会变成:0XC000 00 00(持续的
这个循环的)
那么有没有什么更好的办法呢???
1,将右移变成左移,我们需要将n的二进制中的每一个1都左移到最高位去,然后跟相应的二进制去
与,但是我们得先求出相应的二进制:198的话,二进制就为128,求解的过程也是比较麻烦的,会
浪费更多的时间,去指向更多的指令,所以,不太好
2,换一种想法,我们可以不右移输入的数字n,首先需要把n和1做与运算,判断n的最低位是不是为
1。接着把1左移一位得到2,再和n左运算,就能判断n的次低位是不是1......这样反复左移,每次
都能判断n中的其中一位是不是1。
int flag = 1;
while(flag)
{
if(n& flag)
count++;
flag= flag << 1;
}
这个解法中,循环的次数等于二进制的位数,32位的整数需要循环32次,嗯嗯,还是有点多啊,
假如我的n中只有一个1,那么循环32次多么的浪费啊!!!
3,为了只循环有效的次数(1的个数),我们先来看看一个数减去1的情况:
1,如果一个整数不等于0,那么该整数的二进制表示中至少有一位是1.先假设这个数的最右边一位是1,
那么减去1时,最后一位变成0而其他所有位都保持不变。也就是最后一位相当于取反的操作,由1变成了0.
2,假设最后一位不是1而是0的情况。如果该整数的二进制表示中最右边的1位于第m位,那么减去
1之后,第m位由1变成0,而第m位之后的所有0都变成1
0110(6),减1, 0101(5),可证
所以,我们发现,对于一个整数而言,减去1:都是把最右边的1变成0.如果它的右边还有0的话,所
有的0都变成了1,而它左边所有位都保持不变。接下来我们把一个整数和它减去1的结果做位与运算,
相当于把它最右边的1变成0
1100,1011(位与)(1000)
所以说:我们把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么也就是应
了我们上面所说的话,整数表示的二进制中有多少个1,就可以循环多少次了
intcount = 0;
while(n)
{
count++;
n= n & (n - 1);
}