65.9K
CodeProject 正在变化。 阅读更多。
Home

位运算符简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (131投票s)

2002年5月9日

CPOL

5分钟阅读

viewsIcon

1414650

本文简要概述了 C 风格的位运算符

引言

我注意到有些人似乎在位运算符方面遇到了问题,所以我决定写这篇简短的教程来介绍如何使用它们。

位简介

您可能会问,位是什么?

简单来说,位是我们用计算机做的一切的组成部分,即单独的0和1。您使用的所有数据都存储在计算机中,使用位。一个BYTE由八个位组成,一个WORD是两个BYTE,或十六个位。而一个DWORD是两个WORD,或三十二个位。

 0 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 0 1 0 0 0 1 1 1 1 0 0 0
||              |               |               |              ||
|+- bit 31      |               |               |       bit 0 -+|
|               |               |               |               |
+-- BYTE 3 -----+--- BYTE 2 ----+--- BYTE 1 ----+-- BYTE 0 -----+
|                               |                               |
+----------- WORD 1 ------------+----------- WORD 0 ------------+
|                                                               |
+--------------------------- DWORD -----------------------------+

位运算符的美妙之处在于,您可以将BYTEWORDDWORD用作小型数组或结构。使用位运算符,您可以检查或设置单个位甚至一组位的值。

十六进制数及其与位的关系

在处理位时,用二进制表示法(仅用0和1表示)来表示每个数字是相当困难的。为了解决这个问题,我们使用十六进制(基数为16)数。

您可能知道也可能不知道,需要四个位才能涵盖从零到十五的所有数字,这也恰好是单个十六进制数字的范围。这四位一组,或半个BYTE,称为一个nibble。由于一个BYTE中有两个nibble,我们可以使用两个十六进制数字来表示一个BYTE的值。

NIBBLE   HEX VALUE
======   =========
 0000        0
 0001        1
 0010        2
 0011        3
 0100        4
 0101        5
 0110        6
 0111        7
 1000        8
 1001        9
 1010        A
 1011        B
 1100        C
 1101        D
 1110        E
 1111        F

因此,如果我们有一个BYTE包含字母“r”(ASCII码114),它将如下所示

0111 0010    binary
  7    2     hexadecimal

我们可以将其写为“0x72

位运算符

有六个位运算符。它们是

  1.   &   AND 运算符
  2.    |   OR 运算符
  3.    ^   XOR 运算符
  4.    ~   按位取反(或求反)运算符
  5.   >>   右移运算符
  6.   <<   左移运算符。

与 (&) 运算符

与 (&) 运算符比较两个值,当且仅当被比较的两个值都具有相应的位设置时,它才返回一个具有设置位的计算结果。位使用以下表进行比较

   1   &   1   ==   1
   1   &   0   ==   0
   0   &   1   ==   0
   0   &   0   ==   0

一个理想的用途是设置一个掩码来检查特定位的值。假设我们有一个BYTE包含一些位标志,我们想检查位四是否被设置。

BYTE b = 50;
if ( b & 0x10 )
    cout << "Bit four is set" << endl;
else
    cout << "Bit four is clear" << endl;

这将产生以下计算

    00110010  - b
  & 00010000  - & 0x10
  ----------
    00010000  - result

因此,我们看到位四被设置了。

或 (|) 运算符

或 (|) 运算符比较两个值,如果其中一个值或另一个值,或两者都具有相应的位设置,则它返回一个具有设置位的计算结果。位使用以下表进行比较

   1   |   1   ==   1
   1   |   0   ==   1
   0   |   1   ==   1
   0   |   0   ==   0

一个理想的用途是确保某些位被设置。假设我们想确保某个值的位三被设置

BYTE b = 50;
BYTE c = b | 0x04;
cout << "c = " << c << endl;

这将产生以下计算

    00110010  - b
  | 00000100  - | 0x04
  ----------
    00110110  - result

异或 (^) 运算符

异或 (^) 运算符比较两个值,如果其中一个值或另一个值具有相应的位设置,但不是两者都具有,则它返回一个具有设置位的计算结果。位使用以下表进行比较

   1   ^   1   ==   0
   1   ^   0   ==   1
   0   ^   1   ==   1
   0   ^   0   ==   0

一个理想的用途是切换特定位。假设我们想切换位三和位四

BYTE b = 50;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;

这将产生以下计算

    00110010  - b
  ^ 00011000  - ^ 0x18
  ----------
    00101010  - result

    00101010  - b
  ^ 00011000  - ^ 0x18
  ----------
    00110010  - result

按位取反 (~) 运算符

按位取反 (~) 运算符(或求反)运算符仅作用于一个值并将其反转,将所有1转换为0,所有0转换为1。一个理想的用途是将某些字节设置为0,并确保所有其他字节都设置为1,无论数据大小如何。假设我们想将所有位设置为1,除了位零和位一

BYTE b = ~0x03;
cout << "b = " << b << endl;
WORD w = ~0x03;
cout << "w = " << w << endl;

这将产生以下计算

    00000011  - 0x03
    11111100  - ~0x03  b

    0000000000000011  - 0x03
    1111111111111100  - ~0x03  w

另一个理想的用途是将其与与 (&) 运算符结合使用,以确保某些位设置为0。假设我们想清除位四

BYTE b = 50;
cout << "b = " << b << endl;
BYTE c = b & ~0x10;
cout << "c = " << c << endl;

这将产生以下计算

    00110010  - b
  & 11101111  - ~0x10
  ----------
    00100010  - result

右移 (>>) 和左移 (<<) 运算符

右移 (>>) 和左移 (<<) 运算符将位数移动指定的位数。右移 (>>) 运算符将位从高位移向低位。左移 (<<) 运算符将位从低位移向高位。这些运算符的一个用途是出于任何原因对齐位(请查看 MAKEWPARAMHIWORDLOWORD 宏)

BYTE b = 12;
cout << "b = " << b << endl;
BYTE c = b << 2;
cout << "c = " << c << endl;
c = b >> 2;
cout << "c = " << c << endl;

这将产生以下计算

    00001100  - b
    00110000  - b << 2
    00000011  - b >> 2

位字段

使用位还可以做的另一件有趣的事情是使用位字段。通过位字段,您可以在BYTEWORDDWORD内设置微型结构。例如,假设我们想跟踪日期,但我们想尽量节省内存。我们可以这样声明我们的结构

struct date_struct {
    BYTE day   : 5,   // 1 to 31
         month : 4,   // 1 to 12
         year  : 14;  // 0 to 9999
    } date;

在此示例中,day字段占用最低的5位,month占用接下来的4位,year占用接下来的14位。因此,我们可以将日期结构存储在二十三位中,这包含在三个BYTE中。第二十四位被忽略。如果我为每个字段使用整数声明,该结构将占用12个BYTE

|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
  |                           |       |         |
  +------ year ---------------+ month +-- day --+

现在让我们分解这个声明,看看我们正在做什么。

首先,我们将查看用于位字段结构的. 在这种情况下,我们使用了一个BYTE。一个BYTE是8位,通过使用它,编译器将分配一个BYTE用于存储。但是,如果我们使用的结构超过8位,编译器将分配另一个BYTE,即容纳我们结构的任意数量的BYTE。如果我们使用了WORDDWORD,编译器将分配总共32位来容纳我们的结构。

现在让我们看看各种字段是如何声明的。首先,我们有变量(daymonthyear),后面跟着一个冒号,将变量与其包含的位数分开。每个位字段用逗号分隔,列表以分号结束。

现在我们来看struct声明。我们将位字段放入struct中,这样我们就可以使用约定俗成的结构访问符号来访问结构成员。此外,由于我们无法获取位字段的地址,现在我们可以使用结构的地址。

date.day = 12;

dateptr = &date;
dateptr->year = 1852;
© . All rights reserved.