位运算符

基本的位运算符

&

按位与,规则:若两个相应额二进制位都为1,则该位的结果为1,否则为0

|

按位或,规则:两个相应的二进制位中只要有一个为1,则该位的结果为1,否则为0

^

按位异或,规则:若两个二进制位相同,则结果为0,不同则为1

~

按位求反,规则:安慰取反,即0变1,1变0

<< 

左移

>> 

右移

注意:

位运算符中除“~”以外,均为双目运算符,即要求两侧各有一个运算量。

运算量只能是整型或字符型数据。 

在一定的取值范围内,将一个整数左移1位相当于乘以2。当然,如果左移改变了符号位,或者最高位是1被移出去了,那么结果肯定不是乘以2了,所以说“在一定的取值范围内”。计算机做移位比做乘法快得多,编译器可以利用这一点做优化。 

当操作数是无符号数时,右移运算的规则和左移类似。在一定的取值范围内,将一个整数右移1位相当于除以2,小数部分截掉。和左移类似,移动的位数也必须小于左操作数的总位数,否则结果是Undefined的。 

当操作数是有符号数时,右移运算的规则比较复杂: 

如果是正数,那么高位移入0。 

如果是负数,那么高位移入1还是0不一定,这是Implementation-defined的。 

由于类型转换和移位等问题,使用有符号数做位运算是很不方便的,所以, 建议只对无符号数做位运算,以减少出错的可能。 

掩码

 

如果要对一个整数中的某些位进行操作,可以用掩码(Mask)实现。比如掩码0x0000ff00表示对一个32位整数的8~15位进行操作:

 

(1)取出8~15位:

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = (a & mask) >> 8; /* 0x00000056 */

这样也可以达到同样的效果:

 

b = (a >> 8) & ~(~0 << 8);

 

(2)将8~15位清0:

 

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a & ~mask; /* 0x12340078 */

 

(3)将8~15位置1:

 

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a | mask; /* 0x1234ff78 */

异或运算的一些特性

1、一个数和自己做异或的结果是0。如果需要一个常数0, x86平台的编译器可能会生成这样的指令: xorl %eax, %eax。不管eax寄存器里的值原来是多少,做异或运算都能得到0,这条指令比同样效果的movl $0, %eax指令快。

2、从异或的真值表可以看出,不管是0还是1,和0做异或值不变,和1做异或得到原值的相反值。可以利用这个特性配合掩码实现某些位的翻转,例如:

unsigned int a, b, mask = 1 << 6;
a = 0x12345678;
b = a ^ mask; /* flip the 6th bit */

3、如果a1 ^ a2 ^ a3 ^ ... ^ an的结果是1,则表示a1、2、a3...an之中1的个数为奇数个,否则为偶数个。这条性质可用于奇偶校验( Parity Check),比如在串口通信过程中,每个字节的数据都计算一个校验位,数据和校验位一起发送出去,这样接收方可以根据校验位粗略地判断接收到的数据是否有误。

4、 x ^ x ^ y == y,因为x ^ x == 0, 0 ^ y == y。这个性质有什么用呢?我们来看这样一个问题:交换两个变量的值,不得借助于额外的存储空间,所以就不能采用temp = a; a = b; b =temp;的办法了。利用位运算可以这样做交换:

a = a ^ b;
b = b ^ a;
a = a ^ b;

分析一下这个过程。为了避免混淆,把a和b的初值分别记为a0和b0。第一行, a = a0 ^ b0;第二行,把a的新值代入,得到b = b0 ^ a0 ^ b0,等号右边的b0相当于上面公式中的x, a0相当于y,所以结果为a0;第三行,把a和b的新值代入,得到a = a0 ^ b0 ^ a0,结果为b0。

posted @ 2018-04-05 10:13  刘-皇叔  阅读(324)  评论(0编辑  收藏  举报