C 的位运算符

15.3  C 的位运算符

  C 提供位的逻辑运算符和移位运算符. 在以下例子中, 我们将使用二进制记数法写出值, 以便你可以了解对位发生的操作. 在一个实际程序中, 你可以使用一般形式的整数变量或常量. 例如不使用 00011001 的形式, 而写为 25 或 031 或 0x19 . 在我们的例子中, 我们将使用 8位数字, 从左到右, 每位的编号是 7 到 0.


-------------------------------------------------------------------------------

  15.3.1   位逻辑运算符

  4 个位运算符用于整型数据, 包括 char, 将这些运算符称为位 (bitwise) 运算符的原因是它们对每位进行操作, 而不影响左右两侧的位. 请不要将这些运算和会与常规的逻辑运算符相混淆 (&& || 和 !), 常规的逻辑运算符对整个值进行操作.

-------------------------------------------------------------------------------
 一.  二进制反码或按位取反:  ~

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

  假设 val 是一个 unsigned char 已赋值为 2. 在二进制中, 2 是 00000010. 于是 ~val 的值为 11111101, 或 253. 请注意该运算符不改变 val 的值, 正如 3*val 不改变 val 的值一样; val 仍为 2, 但是该运算符并不创建一个可以在别处使用或被赋值的新值.

  newval = ~val;
  printf ("%d", ~val);

  如果你想将 val 的值变为 ~val, 请使用简单的赋值:

  val = ~val;

---------------------------------------------------------------------

  二. 位与 (AND) : &

  二进制运算符 & 通过对两个操作数逐位进行比较产生一个新值. 对于每个位, 只有两个操作数的对应位都为 1  时结果才为 1 ( 用真/假来描述, 只有两个位操作数都为真结果才为真). 因此:

 (10010011) & (00111101)  // 表达式

的结果是:

 (00010001)      // 结果值

原因是在两个操作数中, 只有位 4 和 0 都为 1.

 C 也有一个组合的位与 = 赋值运算符: &=. 下面两个语句产生相同的最终结果:

 val &= 0377;
 val = val & 0377;

 

--------------------------------------------------------------------------

   三. 位或 (OR ): |

  二进制运算符 | 通过对两个操作数逐位进行比较产生一个新值. 对于每个位, 如果其中任意操作数中对应的位为 1 , 那么结果位就为 1 (用真/假来描述, 如果任意一个位操作数为值, 或两个都为真, 那么结果为真). 因此:

  (10010011) | (00111101)  //表达式

的结果值是 :
  (10111111)    // 结果值

  原因是在除了位 6 之外的所有位上, 两个操作数中至少有一个为 1 .

 C 也有一个组合的位或 = 赋值运算符 : |=

 val |= 0377;

该语句产生与如下语句相同的最终结果:

 val = val | 0377;


---------------------------------------------------------------------------

   四. 位异或:  ^

  二进制运算符 ^ 对两个操作数逐位进行比较. 对于每个位, 如果操作数中的对应位有一个为 1  (但是不都为 1), 那么结果为 1 (用真/假来描述, 如果两个操作数中有一个为真, 但是不都为真, 那么结果为真). 因此

 (10010011) ^ (00111101)  //表达式

的结果值是:

 (10101110)    // 结果值

  请注意, 因为两个操作数中的位 0 都为 1, 因此位 0 的结果为 0.

 C 也有一个组合的位异或 = 赋值运算符:  ^=.

  val ^= 0377;

该语句产生与如下语句相同的最终结果:

  val = val ^ 0377;


-----------------------------------------------------------------------------------

    15.3.2  用法: 掩码

  "位与" 运算符通常跟掩码一起使用. 掩码是某些位设为开 (1) 而某些位设置为关 (0) 的位组合. 要了解称其为掩码的原因, 让我们来看使用 & 将一个数值与掩码相组合时所发生的情况. 例如, 假设你定义符号常量 MASK 为 2 , 即二进制的 00000010, 只有位 1 是非零. 那么:

  flags = flags & MASK;

   这个语句将导致 flags 的除位 1 之外的所有位都被设为 0 , 原因是它的任何位使用 & 运算符与 0 组合都得 0 ; 位 1  将保持不变 (如果该位为 1, 则 1 & 1 为 1; 如果该位为 0, 则 0 & 1 为 0). 因为掩码中的零覆盖了 flags 中的相应的位, 所以该过程称为 "使用掩码".

 
  依此类推, 你可以将掩码中的 0 看作不透明, 将 1 看作透明. 表达式 flags & MASK 就好像使用掩码覆盖 flags 位组合; flags 中的位只有在 MASK 中的对应位是 1  时才可见 (请参见图 15.2).

-------------------------------------------------------
图  15.2  一个掩码

 MASK ->  0 0 0 0 0 0 1 1
              与 (&)
flags ->  1 0 0 1 0 1 1 0
              等于
                      1

-------------------------------------------------------

  你可以通过使用 "与-赋值" 运算符来简化代码, 如下:

  flags &= MASK;

  一种常见的 C  用法如下面语句所示:

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

  回忆一下, 值 0xff 的二进制形式为 11111111, 十进制形式为 0377. 该掩码留下 ch 的最后 8 位, 将其余位设为 0, 无论最初的 ch 是 8 位, 16位或是更多, 都将最终的值修整到一个字节中. 在这个例子中, 掩码宽度为 8 位.


--------------------------------------------------------------------------

  15.3.3  用法: 打开位

 有时, 你可能需要打开一个值中特定的位, 同时保持其他位不变. 例如, 一台 IBM PC 通过将值发送到端口来控制硬件. 比如要打开扬声器, 可能需要打开 1 位, 同时保持其他位不变. 你可以使用 "位或" 运算符来实现.

 例如, 考虑 MASK, 其位 1 设为 1. 下面的语句将 flags 中的位 1 设为 1 , 并保留其他所有位不变:

  flags = flags | MASK;

  这是因为任何位使用 | 运算符与 0 相组合结果为该位本身, 任何位使用 | 运算符与 1 组合结果为 1.
 作为缩写, 你可以使用位或-赋值运算符:

  flags |= MASK;

  同样, 这种方法根据 MASK 中打开的位将 flags 中的对应位设为 1 , 同时保持其他位不变.


-----------------------------------------------------------------------

    15.3.4   用法: 关闭位

   不影响其他位, 同时能够将特定的位关闭与能够将特定的位打开一样是有用的. 假设你想关闭变量 flags 中的位 1. MASK 仍然只有位 1 是打开的. 你可以做如下操作:

 flags = flags & ~MASK;

  因为 MASK 除了位 1  其他位都为 0, 所以 ~MASK 除了位 1 其他位都为 1. 任何位使用 & 与 1 组合的结果为该位本身, 因此该语句除位 1 以外保留其他所有位不变. 任何位使用 & 与 0 组合的结果都为 0, 因此无论位 1 的初始值为何, 都将其设为 0. 你可以使用以下缩写形式:

 flags &= ~MASK;

-----------------------------------------------------------------------------

   15.3.5   用法: 转置位

  转置 (toggling) 一位表示如果该位打开, 则关闭该位: 如果该位关闭, 则打开该位. 你可以使用 "位异或" 运算符来转置一个位. 其思想是如果 b 是一个位 (1或0), 那么如果 b 为 1 则 1^b 为 0, 如果 b 为 0 则 1^b 为1 . 而且, 无论 b 的值是 0 还是 1, 0^b 为 b. 因此, 如果使用 ^ 将一个值与掩码组合, 那么该值中对应掩码位为 1  的位被转置, 对应掩码位为 0 的位不改变. 要转置 flags 中的位 1, 你可以使用以下任意一个语句:

  flags = flags ^ MASK;
  flags ^= MASK;

--------------------------------------------------------------------------------

   15.3.6  用法:  查看一位的值

  你已经了解改变一位的值的方法. 然而, 假设你希望查看一位的值. 例如, flags 的位 1 是否为 1 ? 你不应该简单地比较 flags 与 MASK :

 if (flags == MASK)
 puts ("Wow!");      /* 不能正确工作 */

  即使 flags 中的位 1 被设为 1, flags 中的其他位也会使比较结果为非真. 你必须屏蔽 flags 中的其他位, 以便只把 flags 中的位 1 和 MASK 相比较:

if ((flags & MASK) == MASK)
    puts ("Wow~");

  位运算符的优先级低于 == , 因此需要在 falgs & MASK 的两侧加上圆括号.

  为了避免信息漏过边界, 位掩码至少应该与其所屏蔽的值具有相同的宽度.


-----------------------------------------------------------------------------

   15.3.7  移位运算符

  现在让我们了解一下 C 的移位运算符. 移位运算符将位向左或向右移. 同样, 我们仍将明确地使用二进制形式来说明该机制的工作原理.

--------------------------------------------------------------------------
 
  一. 左移: <<

  左移运算符 << 将其左侧操作数的值的每位向左移动, 移动的位数由其右侧操作数指定. 空出的位用 0 填充, 并且丢弃移出左侧操作数末端的位. 在以下例子中, 每位向左移动两个位置.

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

  该操作产生一个新位值, 但是不改变其操作数. 例如, 假设 stonk 为 1 , 则 stonk << 2 为 4, 但是 stonk 仍为 1. 你可以使用左移-赋值运算符 (<<=) 来实际改变一个变量的值. 该运算符将变量中的位向左移动右侧值大小的位置. 如下例:

int stonk = 1;
int onkoo;
onkoo = stonk << 2;  /* 将 4 赋值给 onkoo */
stonk <<= 2;   /* 将 stonk 变成 4 */

----------------------------------------------------------------------------

   二. 右移 : >>

   右移位运算符 >> 将其左侧操作数的值的每位向右移动, 移动的位数由其右侧操作数指定. 丢弃移出左侧操作数右端的位. 对于 unsigned 类型, 使用 0 填充左端空出的位. 对于有符号类型, 结果依赖于机器. 空出的位可能用 0 填充, 或者使用符号 (最左端的) 位的副本填充.

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

  对于无符号值, 有以下结果:

(10001010)  >> 2  /* 表达式, 无符号值*/
(00100010)        /* 所有系统上的结果值 */

  每位向右移动两个位置, 空出的位用 0 填充.

  右移-赋值运算符 (>>=) 将左侧变量的位向右移动指定数量的位置, 如下所示:

int sweet = 16;
int ooosw;
ooosw = sweet >> 3;  /* ooosw = 2, sweet 仍然为 16 */
sweet >>= 3;         /* sweet 变为 2 */

------------------------------------------------------------------------

   三. 用法: 移位运算符

  移位运算符能够提供快捷, 高效的 (依赖于硬件) 对于 2 的幂的乘法和除法.

------------------------------------------------------------------------
 number << n            number 乘以 2 的 n 次幂
------------------------------------------------------------------------
 number >> n            如果 number 非负, 则用 number 除以 2 的 n 次幂
------------------------------------------------------------------------

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

   移位运算符也用于从较大的单位中提取多组比特位. 例如, 假设你使用一个 unsigned long 值代表颜色值, 其中低位字节存放红色亮度, 下一个字节存放绿色亮度, 第三个字节存放蓝色亮度. 假设随后你希望将每种颜色的亮度存储在各自的 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 位颜色值移动到低位字节, 然后使用掩码技术将低位字节赋给所需的变量.


---------------------------------------------------------------------------

    15.3.8    编程实例

  在第 9 章 "函数" 中, 我们使用递归方法编写了一个程序, 将数字转换为它的二进制表示形式, 在这里我们将使用移位运算符完成同样的任务. 程序清单 15.1 中的程序从键盘读取一个整数, 将该整数和一个字符串地址传送给一个名为 itobs() 的函数 (代表 interger to binary string). 然后, 该函数使用移位运算符计算出正确的 1 和 0 的组合, 并存放到字符串中.


  程序清单  15.1  binbit.c  程序
--------------------------------------------------------------
/*  binbit.c  ---  使用位运算显示二进制数 */
#include <stdio.h>
char * itobs (int, char *);
void show_bstr (const char *);

int main (void)
{
    char bin_str [8 * sizeof (int) + 1 ];
    int number;

    puts ("Enter integers and see them in binary .");
    puts ("Non - numberic input terminates program .");
    while (scanf ("%d", &number) == 1)
    {
         itobs (number, bin_str);
         printf ("%d is ",number);
         show_bstr (bin_str);
         putchar ('\n');
     }
     puts ("Bye!");
     return 0;
 }

char * itobs (int n, char * ps)
{
     int i;
     static int size = 8 * sizeof (int);
 
     for (i = size -1; i >= 0; i--, n>>= 1 )
         ps[i] = (01 & n) + '0';
     ps[size] = '\0';
     return ps;
}

/* 4 位一组显示二进制字符串 */
void show_bstr (const char * str)
{
     int i = 0;
     while (str[i])  /* 不是一个空字符 */
    {
        putchar (str[i]);
        if (++i % 4 == 0  && str[i])
        putchar ('  ');
     }
}

  程序清单 15.1 假设系统使用 8 位表示一个字节. 因此, 表达式 8*sizeof (int) 是一个 int 的位数. 考虑到结尾的空字符, bin_str 数组的元素个数为这个表达式的值再加 1.

  因为 itobs() 函数返回的地址与传送给该函数的地址相同, 所以你可以将该函数为 printf() 的参数来使用. 首次执行 for 循环时, 该函数求 01&n 的值. 01 是一个掩码的八进制表示形式, 该掩码除位 0 之外的所有位都设为 0. 因此, 01&n 就是 n 的最后一位的值. 该值为 0 或 1, 但是字符数组需要的是字符 '0'或字符'1'. 对该值加上 '0' 的 ASCII 编码可以完成该转换. 结果放置在数组的倒数第 2 个元素中 (保留最后的元素存放空字符).
  顺便提一下, 你也可以用 1&n 代替 01&n. 使用八进制的 1 而不是十进制的 1 看起来会更接近计算机一些.

  然后, 该循环执行语句 i-- 和 n >>= 1. 第一个语句移动到数组中的前一个元素, 第二个语句将 n 中的位向右移动一个位置. 下次执行循环时, 代码得到新的最右端的位的值. 然后, 将在京的数字字符放置在最后数字前面的元素中. 使用这种方式, 该函数从右向左填充数组.

  你可以使用 printf() 或 put() 函数来显示结果字符串, 而程序清单 15.1 定义了 show_bstr() 函数, 它把每 4 位分成一组以便读出字符串.

  下面是一个运行示例:

Enter integers and see them in binary .
Non - numberic input terminates program .
7
7 is 0000 0000 0000 0000 0000 0000 0000 0111
2005
2005 is 0000 0000 0000 0000 0000 0111 1101 0101
-1
-1 is 1111 1111 1111 1111 1111 1111 1111 1111
32123
32123 is 0000 0000 0000 0000 0111 1101 0111 1011
q
Bye!

-----------------------------------------------------------------------------------

   15.3.9   另一个实例

  让我们讨论另一个例子. 这次的目的是编写一个函数, 该函数反转一个值中的最后的 n 位, 参数为 n 和 要反转的值.

  ~ 运算符可以反转位, 但是该运算符反转一个字节中所有的位, 而不是选定的少数位. 然而, 正如你已经看到的, ^ 运算符 (异或) 可以用于转置单个位. 假设你创建一个掩码, 该掩码最后 n 位设为 1, 其余的位设为 0. 然后, 对该掩码和一个值使用 ^ 运算就可以转置 (即反转) 这个值的最后 n 位, 同时保留该值的其他位不变. 这就是下面所使用的方法:

int invert_end (int num, int bits)
{
   int mask = 0;
   int bitval = 1;

   while (bits-- > 0)
   {
      mask |= bitval;
      bitval <<= 1;
   }
   return num ^ mask;

  while 循环创建该掩码. 最初, mask 所有位都被设为 0. 第一次执行该循环将位 0 设为 1, 然后将 bitval 增加到 2; 也就是将 bitval 的位 0 设为 0, 位 1 设为 1. 下次执行循环时, 将 mask 的位 1 设为 1, 依此类推. 最后, num^mask 运算产生所需的结果.

  要测试该函数, 你可以将其插入前面的程序, 如程序清单 15.2 所示.


  程序清单  15.2  invert4.c 程序
----------------------------------------------------------------
/* invert4.c ---  使用位操作符来显示二进制 */
#include <stdio.h>
char * itobs (int n, char * ps);
void show_bstr (const char *);
int invert_end (int num, int bits);

int main (void)
{
    char bin_str [8 * sizeof (int) + 1 ];
    int number;

    puts ("Enter integers and see them in binary .");
    puts ("Non-numeric input terminates program.");
    while (scanf ("%d", &number) == 1)
    {
       itobs (number, bin_str);
       printf ("%d is \n", number);
       show_bstr (bin_str);
       putchar ('\n');
       number = invert_end (number, 4);
       printf ("Inverting the last 4 bits gives \n");
       show_bstr (itobs (number, bin_str));
       putchar ('\n');
   }
   puts("Bye !");
   return 0;
}

char * itobs (int n, char * ps)
{
    int i;
    static int size = 8 * sizeof (int);
  
    for (i = size - 1; i >= 0; i--, n >>= 1)
       ps[i] = (01 & n) + '0';
    ps[size] = '\0';
    return ps;
}

/* 4 位一组显示二进制字符串 */
void show_bstr (const char * str)
{
    int i = 0;

    while (str[i])  /* 不是一个空字符 */
    {
        putchar (str[i]);
        if (++i % 4 == 0  &&  str[i])
        putchar ( '  ');
    }
}

int invert_end (int num, int bits)
{
    int mask = 0;
    int bitval = 1;

    while (bits-- > 0)
    {
       mask |= bitval;
       bitval <<= 1;
    }
    return num ^ mask;
}


   下面是一个运行示例:

Enter integers and see them in binary .
Non-numeric input terminates program.
7
7 is
0000 0000 0000 0000 0000 0000 0000 0111
Inverting the last 4 bits gives
0000 0000 0000 0000 0000 0000 0000 1000
12541
12541 is
0000 0000 0000 0000 0011 0000 1111 1101
Inverting the last 4 bits gives
0000 0000 0000 0000 0011 0000 1111 0010
q
Bye !

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值