C

C 知识量:16 - 74 - 317

15.2 按位运算符><

按位逻辑运算符- 15.2.1 -

在对位操作方面,C提供按位逻辑运算符和移位运算符两种运算符。

其中,4个按位逻辑运算符都用于整型数据,包括char。之所以叫作按位运算,是因为这些操作都是针对每一个位进行,不影响它左右两边的位,而常规的逻辑运算符操作的是整个值。

1、 二进制反码或按位取反:~

一元运算符~把1变为0,把0变为1。例如:

~(10011010)    // 表达式
(01100101)      // 结果值

如果,val的值是2,在二进制中,00000010表示2,那么,~val的值是11111101,即253。但是,该运算符不会改变val的值,就像2 * val不会改变val的值一样,val仍然是2。

2、按位与:&

二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1。例如:

(10010011) & (00111101)    // 表达式
(00010001)                 // 结果值

此外,C有一个按位与和赋值结合的运算符:&=。下面两条语句产生的最终结果相同:

val &= 0377;
val = val & 0377;

3、按位或:|

二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位至少有一个为1,结果就为1。例如:

(10010011) | (00111101) // 表达式
(10111111)              // 结果值

此外,C有一个按位或和赋值结合的运算符:|=。下面两条语句产生的最终作用相同:

val |= 0377;
val = val | 0377;

4、按位异或:^

二元运算符^逐位比较两个运算对象。对于每个位,如果两个运算对象中相应的位不同,结果为1。例如:

(10010011) ^ (00111101) // 表达式
(10101110)              // 结果值

此外,C有一个按位异或和赋值结合的运算符:^=。下面两条语句产生的最终作用相同:

val ^= 0377;
val = val ^ 0377;

掩码- 15.2.2 -

按位与运算符常用于掩码(mask)。所谓掩码指的是一些设置为开(1)或关(0)的位组合。假设定义符号常量MASK为2(即,二进制形式为00000010),只有1号位是1,其他位都是0。下面的语句:

flags = flags & MASK;

把flags中除1号位以外的所有位都设置为0,因为使用按位与运算符(&)时,任何位与0组合都得0。1号位的值不变(如果1号位是1,那么1&1得1;如果1号位是0,那么0&1也得0)。这个过程叫作“使用掩码”,因为掩码中的0隐藏了flags中相应的位。

可以把掩码中的0看作不透明,1看作透明。表达式flags & MASK相当于用掩码覆盖在flags的位组合上,只有MASK为1的位才可见。

用&=运算符可以简化前面的代码,例如:

ch &= 0xff; /* 或者 ch &= 0377; */

oxff的二进制形式是11111111,八进制形式是0377。这个掩码保持ch中最后8位不变,其他位都设置为0。无论ch原来是8位、16位或是其他更多位,最终的值都被修改为1个8位字节。在该例中,掩码的宽度为8位。

打开位- 15.2.3 -

有时,需要打开一个值中的特定位,同时保持其他位不变。例如,为了打开内置扬声器,必须打开1号位,同时保持其他位不变。这种情况可以使用按位或运算符(|)。例如:假设定义符号常量MASK为2(即,二进制形式为00000010),只有1号位是1,其他位都是0。下面的语句:

flags = flags | MASK;

把flags的1号位设置为1,且其他位不变。因为使用|运算符,任何位与0组合,结果都为本身;任何位与1组合,结果都为1。用|=运算符可以简化上面的代码,如下所示:

flags |= MASK;

关闭位- 15.2.4 -

和打开特定的位类似,有时也需要在不影响其他位的情况下关闭指定的位。假设要关闭变量flags中的1号位。同样,MASK只有1号位为1(即,打开)。可以这样做:

flags = flags & ~MASK;

由于MASK除1号位为1以外,其他位全为0,所以~MASK除1号位为0以外,其他位全为1。使用&,任何位与1组合都得本身,所以这条语句保持1号位不变,改变其他各位。另外,使用&,任何位与0组合都的0。所以无论1号位的初始值是什么,都将其设置为0。

假设flags是00001111,MASK是10110110,那么:

flags & ~MASK
即:
(00001111) & ~(10110110)    // 表达式 
(00001001)                   // 结果值

MASK中为1的位在结果中都被设置(清空)为0。flags中与MASK为0的位相应的位在结果中都未改变。

可以使用下面的简化形式:

flags &= ~MASK;

切换位- 15.2.5 -

切换位指的是打开已关闭的位,或关闭已打开的位。可以使用按位异或运算符(^)切换位。

例如,假设flags是00001111,MASK是10110110,那么:

flags ^ MASK
即:
(00001111) ^ (10110110)    // 表达式
(10111001)                 // 结果值

flags中与MASK为1的位相对应的位都被切换了,MASK为0的位相对应的位不变。

可以使用下面的简化形式:

flags ^= MASK;

检查位的值- 15.2.6 -

有时,需要检查某位的值。例如,flags中1号位是否被设置为1?不能这样直接比较flags和MASK:

flags == MASK /* 不能正常工作 */

这样做即使flags的1号位为1,其他位的值会导致比较结果为假。因此,必须覆盖flags中的其他位,只用1号位和MASK比较:

(flags & MASK) == MASK

由于按位运算符的优先级比==低,所以必须在flags & MASK周围加上圆括号。此外,为了避免信息漏过边界,掩码至少要与其覆盖的值宽度相同。

移位运算符- 15.2.7 -

移位运算符用于向左或向右移动位。

1、左移:<<

左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。

(10001010) << 2    // 表达式
(00101000)         // 结果值

该操作产生了一个新的位值,但是不改变其运算对象。例如,假设a为1,那么a<<2为4,但是a本身不变,仍为1。

可以使用左移赋值运算符(<<=)来更改变量的值。该运算符将变量中的位向左移动其右侧运算对象给定值的位数。例如:

int a = 1;
int b;
b = a << 2;     // 把4赋给b
a <<= 2;        // 把a的值改为4

2、右移:>>

右移运算符(>>)将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢。对于无符号类型,用0填充空出的位置;对于有符号类型,其结果取决于机器。空出的位置可用0填充,或者用符号位(即,最左端的位)的副本填充。例如:

(10001010) >> 2        // 表达式,有符号值
(00100010)             // 在某些系统中的结果值
(10001010) >> 2        // 表达式,有符号值
(11100010)             // 在另一些系统上的结果值

(10001010) >> 2        // 表达式,无符号值
(00100010)             // 所有系统都得到该结果值

右移赋值运算符(>>=)将其左侧的变量向右移动指定数量的位数。例如:

int a = 16;
int b;
b = a >> 3;    // b = 2,a的值仍然为16
a >>=3;        // a的值为2

移位运算符的应用- 15.2.8 -

1、移位运算符针对2的幂提供快速有效的乘法和除法:

  • number << n        number乘以2的n次幂。

  • number >> n        如果number为非负,则用number除以2的n次幂。

这些移位运算符类似于在十进制中移动小数点来乘以或除以10。

2、移位运算符还可用于从较大单元中提取一些位。例如,假设用一个unsigned long类型的值表示颜色值,低阶位字节储存红色的强度,下一个字节储存绿色的强度,第3个字节储存蓝色的强度。如果要把每种颜色的强度分别储存在3个不同的unsigned char类型的变量中。那么,可以使用下面的语句:

#define BYTE_MASK 0xff
unsigned long color = 0x002a162f;
unsigned char blue, green, red;
red = color & BYTE_MASK;
green = (color >> 8) & BYTE_MASK;
blue = (color >> 16) & BYTE_MASK;

以上代码中,使用右移运算符将8位颜色值移动至低阶字节,然后使用掩码技术把低阶字节赋给指定的变量。