关于位操作的一些技巧小结

网上也有很多关于位处理操作的东西,也都写的很详细,但也有很多人再问关于这方面的问题,自己最近在看《hacker's delight》,那里面有很多有趣的东东,然后写了一些读书笔记...

在C语言中的位处理操作符&,|,~,^<<和>>,以及相应的复制操作符&=,|=,~=,^=,<<=,>>=, 都只对具有整型的操作数有效,并返回一个整型值,因为这些操作处理的实际上是整型值的位, 在这里,为了方便起见,我们只讨论无符号数,即unsigned int,关于位操作的基本知识这里就不多介绍了, 详细的可以看任何一本关于C语言的书(很多书都介绍了)或者看看微机原理.

先热热身,来看最简单的 X*9, 大部分编译器都会优化成X+(X<<3),因为计算乘法的开销比移位大的多.
Ok,在接着看,X&(-X)表达式,例如:
 X     10100110
-X     01011010 (-X就是取X的补码,先对所有位变反,在最低位+1)
X&(-X) 00000010
结果就是除了含1的最低位外,所有的为都清零了,然后再看看X&(X-1):
X              10100110
X-1          10100101
X&(X-1)  10100100
在这里结果是含1的最低位被清零,在看看X|(X+1):
X             10100110
X+1        10100111
X|(X+1)  10100111
在这里结果是含0的最低位变成1,

好了,下面在介绍一些更有趣的东西:
首先,讨论一下关于位的翻转,最常用的方法就是,用一个循环,一个个的颠倒,这样的复杂度是O(n),
当然也有个更快的,就是设置一个char rev[256]的数组,将每个数翻转的结果记录下来,然后如果需要
用到某一个的时候,直接提取,这样的复杂度是O(1),当然,是有额外的空间开销,下面介绍一个O(logn)
的算法,不需要用到额外的空间,先看代码:
unsigned rev(unsigned X)
{
   X=(X&0x55)<<1 | (X&0xAA)>>1;
   X=(X&0x33)<<2 | (X&0xCC)>>2;
   X=(X&0x0F)<<4 | (X&0xF0)>>4;
   return X;
}
假设X是8位的ABCDEFGH;
X&0x55 得到 0B0D0F0H,左移一位得到B0D0F0H0;
X&0xAA 得到 A0C0E0G0,右移一位得到0A0C0E0G;
相与得到BADCFEHG;
然后
X&0x33 得到 00DC00HG,左移两位得到DC00HG00
X&0xCC 得到 BA00FE00,右移两位得到00BA00FE
相与得到DCBAHGFE
接着
X&0x0F 得到0000HGFE,左移四位得到HGFE0000
X&0xF0 得到DCBA0000,右移思维得到0000DCBA
相与得到
HGFEDCBA(搞定)

接着在介绍关于计算位中含有1的个数,常用算法思想就是用for循环,一个个的计算,复杂度O(n),
当然也有一个o(1)的算法,和上面的那个思想一样,当然,我们这里依旧介绍一个O(logn)的算法:
先看代码:
unsigned count(unsigned X)
{
  X=(X&0x55) + ((X>>1)&0x55);
  X=(X&0x33) + ((X>>2)&0x55);
  X=(X&0x0F) + ((X>>4)&0x0F);
  return X;
}
假设X是8位的ABCDEFGH;
X&0x55      得到 0B0D0F0H,
(X>>1)&0x55 得到 0A0C0E0G,
相加得到0|(B+A)|0|(D+C)|0|(E+F)|0|(H+G), 其中用|来分隔开每一位,这样更好看清楚:)
然后
X&0x33      得到 0|0|0|(D+C)|0|0|0|(H+G),
(X>>2)&0x33 得到 0|0|0|(B+A)|0|0|0|(F+E),
对应位相加得到   0|0|0|(D+C+B+A)|0|0|0|(H+G+F+E)
接着
X&0x0F      得到0|0|0|0|0|0|0|(H+G+F+E),
(X>>4)&0x0F 得到0|0|0|0|0|0|0|(D+C+B+A),
相加得到
H+G+F+E+D+C+B+A(各位相加得到的不就是1的个数吗?)

呵呵,很有趣吧,再介绍几个好玩的,可能平时用的不多,
例如给定0000 ABCD,
要变换成0A0B 0C0D,
程序如下所示:
呵呵,如果X的高4位不为零,可以附加一句 X=X&0x0F;
X=(X<<2)|X) &0x33;
X=(X<<1)|X) &0x55;
嗯,这里我就不具体的给分析过程了,有兴趣的自己一个走走这个过程,很有趣的哦
现在在考虑一下
给定abcd ABCD
变成aAbB cCdD
嗯,这次能按照上面那样来吗?不妨动手写写,在这里给个答案(不唯一哦)
X=(X&0x30)>>2 | (X&0x0C)<<2 | X&0xC3 ;
X=(X&0x44)>>1 | (X&0x22)<<1 | X&0x99 ;

嗯,在给另外的一个答案,需要用到与或:
t= (X^(X>>2)) & 0x0C;  X= X^t^(t<<2);
t= (X^(X>>1)) & 0x22;  X= X^t^(t<<1);

其实这两种方法的思想都是一样的
首先先把abcd ABCD变成abAB cdCD
然后再变成我们所需的 aBbB cCdD
有兴趣的可以变成AaBb CcDd玩玩哦


如果对位处理感兴趣,详细请可以参考《Hacker's Delight》这本书,上面有好多位处理的技巧:) 

参考文献

[1] Henry S. Warren. Hacker's Delight. Addison Wesley. July 17, 2002.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值