C语言位运算

一 取出每一个bit位的值

  取出每一个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=a9,分析 a ∗ 9 a*9 a9可以拆分成 a ∗ ( 8 + 1 ) a*(8+1) a(8+1) a ∗ 8 + a ∗ 1 a*8+a*1 a8+a1,因此可以改为: a = ( a < < 3 ) + a a=(a<<3)+a a=(a<<3)+a a = a ∗ 7 a=a*7 a=a7,分析 a ∗ 7 a*7 a7可以拆分成 a ∗ ( 8 − 1 ) a*(8-1) a(81) a ∗ 8 − a ∗ 1 a*8-a*1 a8a1,因此可以改为: a = ( a < < 3 ) − a a=(a<<3)-a a=(a<<3)a
  关于除法读者可以类推,此略。

四 什么样的数据类型可以直接移位

  charshortintlongunsigned charunsigned shortunsigned intunsigned long都可以进行移位操作,而doublefloatboollong double则不可以进行移位操作。

五 有符号数据类型的移位操作

  对于charshortintlong这些有符号的数据类型:
  对负数进行左移:符号位始终为1,其他位左移;
  对正数进行左移:所有位左移,即<<,可能会变成负数;
  对负数进行右移:取绝对值,然后右移,再取相反数;
  对正数进行右移:所有位右移,即>>。

六 无符号数据类型的移位操作

  对于unsigned charunsigned shortunsigned intunsigned long这些无符号数据类型:没有特殊要说明的,使用<<和>>操作符就OK了。

七 C/C++移位运算符出界后的结果是不可预期的

  以前看到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.

你可能感兴趣的:(C/C++)