取出每一个bit位的值,是0或者1。
#include
int main()
{
char byData = 0x36; //0110 1100
int n0, n1, n2, n3, n4, n5, n6, n7;
n0 = (byData & 0x01) == 0x01 ? 1 : 0;
n1 = (byData & 0x02) == 0x02 ? 1 : 0;
n2 = (byData & 0x04) == 0x04 ? 1 : 0;
n3 = (byData & 0x08) == 0x08 ? 1 : 0;
n4 = (byData & 0x10) == 0x10 ? 1 : 0;
n5 = (byData & 0x20) == 0x20 ? 1 : 0;
n6 = (byData & 0x40) == 0x40 ? 1 : 0;
n7 = (byData & 0x80) == 0x80 ? 1 : 0;
printf("0-%d, 1-%d, 2-%d, 3-%d, 4-%d, 5-%d, 6-%d, 7-%d\n", n0, n1, n2, n3, n4, n5, n6, n7);
return 0;
}
算术移位和逻辑移位主要是针对右移而言:算数移位用符号位作为填充,逻辑移位不填充直接补零。
而左移时总是移位,然后补零。
例如:
1、127的补码:0111 1111(127>>1)
右移一位: 0011 1111 -> 原码同补码一样 对应十进制:63
右移二位: 0001 1111 -> 原码同补码一样 对应十进制:31
右移三位: 0000 1111 -> 原码同补码一样 对应十进制:15
右移四位: 0000 0111 -> 原码同补码一样 对应十进制:7
右移五位: 0000 0011 -> 原码同补码一样 对应十进制:3
右移六位: 0000 0001 -> 原码同补码一样 对应十进制:1
右移七位: 0000 0000 -> 原码同补码一样 对应十进制:0
右移八位: 0000 0000 -> 原码同补码一样 对应十进制:0
2、-128的补码:1000 0000
右移一位: 1100 0000 -> 这个补码对应的原码为:1100 0000 对应十进制:-64
右移二位: 1110 0000 -> 这个补码对应的原码为:1010 0000 对应十进制:-32
右移三位: 1111 0000 -> 这个补码对应的原码为:1001 0000 对应十进制:-16
右移四位: 1111 1000 -> 这个补码对应的原码为:1000 1000 对应十进制:-8
右移五位: 1111 1100 -> 这个补码对应的原码为:1000 0100 对应十进制:-4
右移六位: 1111 1110 -> 这个补码对应的原码为:1000 0010 对应十进制:-2
右移七位: 1111 1111 -> 这个补码对应的原码为:1000 0001 对应十进制:-1
右移八位: 1111 1111 -> 这个补码对应的原码为:1000 0001 对应十进制:-1
移位实现的乘除法比直接乘除的效率高很多。
a=a*4;
b=b/4;
可以改为:
a=a<<2;
b=b>>2;
说明:除2 = 右移1位,乘2 = 左移1位,除4 = 右移2位,乘4 = 左移2位,除8 = 右移3位,乘8 = 左移3位。通常如果需要乘以或除以2的n次方,都可以用移位的方法代替。
大部分的C编译器,用移位的方法得到代码比调用乘除法子程序生成的代码效率高。
实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如: a = a ∗ 9 a=a*9 a=a∗9,分析 a ∗ 9 a*9 a∗9可以拆分成 a ∗ ( 8 + 1 ) a*(8+1) a∗(8+1)即 a ∗ 8 + a ∗ 1 a*8+a*1 a∗8+a∗1,因此可以改为: a = ( a < < 3 ) + a a=(a<<3)+a a=(a<<3)+a; a = a ∗ 7 a=a*7 a=a∗7,分析 a ∗ 7 a*7 a∗7可以拆分成 a ∗ ( 8 − 1 ) a*(8-1) a∗(8−1)即 a ∗ 8 − a ∗ 1 a*8-a*1 a∗8−a∗1,因此可以改为: a = ( a < < 3 ) − a a=(a<<3)-a a=(a<<3)−a。
关于除法读者可以类推,此略。
char
、short
、int
、long
、unsigned char
、unsigned short
、unsigned int
、unsigned long
都可以进行移位操作,而double
、float
、bool
、long double
则不可以进行移位操作。
对于char
、short
、int
、long
这些有符号的数据类型:
对负数进行左移:符号位始终为1,其他位左移;
对正数进行左移:所有位左移,即<<,可能会变成负数;
对负数进行右移:取绝对值,然后右移,再取相反数;
对正数进行右移:所有位右移,即>>。
对于unsigned char
、unsigned short
、unsigned int
、unsigned long
这些无符号数据类型:没有特殊要说明的,使用<<和>>操作符就OK了。
以前看到C++标准上说,移位运算符(<<、>>)出界时的行为并不确定:The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.
Intel CPU的移位运算有关。
#include
void main()
{
unsigned int i,j;
i=35;
//为什么下面两个左移操作结果不一样?
j=1<<i; // j为8
j=1<<35; // j为0
}
原因是这样的:i=35;j=1<这两句在VC没有做优化的情况下,将被编译成下面的机器指令:
mov dword ptr [i],23h
mov eax,1
mov ecx,dword ptr [i]
shl eax,cl
mov dword ptr [j],eax
在shl
一句中,eax=1,cl=35
。而Intel CPU执行shl指令时,会先将cl与31进行and操作,以限制左移的次数小于等于31。因为35 & 31 = 3
,所以这样的指令相当于将1左移3位,结果是8。
而j=1<<35;
,一句是常数运算,VC即使不做优化,编译器也会直接计算1<<35的结果。VC编译器发现35大于31时,就会直接将结果设置为0。这行代码编译产生的机器指令是:mov dword ptr [j],0
。
对上面这两种情况,如果把VC编译器的优化开关打开(比如编译成Release版本),编译器都会直接将结果设置为0。
所以,在C/C++语言中,移位操作不要超过界限,否则,结果是不可预期的。
下面是Intel文档中关于shl指令限制移位次数的说明:The destination operand can be a register or a memory location. The count operand can be an immediate value or register CL. The count is masked to 5 bits, which limits the count range to 0 to 31. A special opcode encoding is provided for a count of 1.