C++ 中的位操作符

时间:2022-02-03 17:46:20

位简介

bits,你会问他们是什么呢?
其实,简单说,我们在电脑上处理各种工作都是由许多1和0完成的。我们在电脑上存储的所有数据都是用bits来表示的。一个byte是用8个bit表示的,一个WORD是用两个BYTE表示的,或者16个bit。一个DWORD是用两个WORD表示的,或者32个bit.

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 -----------------------------+

使用位操作符的美妙之处在于你可以把BYTE, WORD 或 DWORD 当成一个小型的数组或者结构。你可以使用位操作符检查单独的一个位的值,设置某一个或一组位的值。

十六进制和他们与bits的关系

使用bits时,很难只使用二元符号1或者0去表示每个数值。为了解决这个问题我们使用十六进制(基数是16)数。

十六进制使用四个二进制位来表示从0到15的数字,这些数字也是单个的十六进制阿拉伯数字所能表示的范围。由四个二进制位或一个BYTE的一半组成的组被称为一个元组。一个BYTE包含两个元组,所以我们可以使用两个十六进制阿拉伯数字来表示一个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-译者注)时,返回的值相应位才被设置。这些比较位使用下面的表进行比较:

1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0
这个操作符理想的应用是建立一个掩码来检查某个位的值。假设我们有一个包含某些位标志的字节,我们想检查它的位4是否被设置(即是否被置1-译者注):

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

因此我们知道位4被置1了。

| 操作符

| 操作符比较两个数,只有他们相应位中的一个或两个同时被设置时,返回值相应位就会被设置。这些比较位使用下面的表进行比较:

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

这个操作符的理想应用是确保某个位被设置。假设我们想某个值的位3一定被设置:

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,而不管这个数据的大小。假设除了位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

>>和<<操作符

>>(右移)和<<(左移)操作符按指定的位数移动位组。>>操作符将位组从高位向低位移。<<操作符将位组从低位向高位移。这两个操作符的一个应用是由于某些原因(如,检验MKEWPARAM, HIWORD, 和 LOWORD宏)需要对齐位组。

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

位域

另一个可以使用位的有意思的事是使用位域。你可以使用位域在BYTE,WORD或DWORD内建立更小的结构。例如,假设我们想知道日期,但我们我想尽可能使用较少的内存。我们可以像下面这样建立数据结构:

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

在这个例子中,'日'占用了5个位,'月'占用了接下来的4位,同时'年'占用了接下来的14位。位24不用。如果我用整型定义每个域,这个结构将占用12字节。

|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位的空间,编译器将分配另外的8位BYTE,直到能容纳我们的结构为止。如果我们使用了WORD或DWORD,编译器将分配一个总共32位的空间容纳我们的结构。
现在,我们来看下不同的域是怎样声明的。首先,我们使用冒号分开域名和位数。既然我们能获得位域的地址,我们就能使用这个结构的地址。

date.day = 12;

dateptr = &date;
dateptr->year = 1852;