前言
我注意到有些人对位运算感到困惑,所以我决定写这篇简单的教程来说明位运算如何操作。
位简介
位,它是什么?你可能会问。
简单来说,位就是1和0,在电脑中做的每一件事都是由它们组成的。电脑中所有的数据使用的是位。一个字节由8个位组成;一个字由两个字节组成,即16个位;而一个双字由四个字节组成,即32个位。
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 -----------------------------+
使用字节,字或者双字来进行位操作显得比较美观,就像使用一个小型数组或结构。使用位运算,可以检查或设置单独某一位的值或组位的值。
十六进制数和位的关系
人们发现,使用二进制计数法表示数字比较的困难。为避免这一问题,采用十六进制计数法(基数为16)。
十六进制的一位数字从0到15分别用二进制的四位来表示。四位一组,即半字节。一个字节有两个半字节,则可以用两位十六进制数表示一个字节的值。
半字节 十六进制数
====== =========
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
如果有一个字节,内容为字母‘r’(ASCII 码 114),则表示如下:
0111 0010 二进制数
7 2 十六进制数记为:0x72
位运算符
共有6种位运算符,如下:
& 与运算符
| 或运算符
^ 异或运算符
~ 取反运算符
>> 右移运算符
<< 左移运算符
& 运算符
&(与)运算要求有两个运算值,然后返回一个值,当且仅当两个运算值都位1时,返回值为1。如下表:
1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0
一个字节可以包含位标志,而使用与运算可以通过设置掩码来检查某位的值。算法如下:它用来判断字节中的第四位是否为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
所以,第四位为1。
| 运算符
|(或)运算符要求两个运算值,然后返回一个值,当且仅当两个运算值中有一个为1或都为1时,返回值为1。如下表:
1 | 1 == 1
1 | 0 == 1
0 | 1 == 1
0 | 0 == 0
使用或运算可以保证字节中的某位为1。算法如下:它用来保证第二位总是为1
BYTE b = 50;
BYTE c = b | 0x04;
cout << "c = " << c << endl;通过以下计算可以得到结果:
00110010 - b
| 00000100 - | 0x04
----------
00110110 - result
^ 异或运算符
^ (异或)运算符要求有两个运算值,然后返回一个值,当且仅当两个运算值中有一个为1但不同时为1时,返回值为1。如下表:
1 ^ 1 == 0
1 ^ 0 == 1
0 ^ 1 == 1
0 ^ 0 == 0
使用异或运算可以翻转特定的位。即0变1,1变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,而第一和第零位置0BYTE b = ~0x03;
cout << "b = " << b << endl;
WORD w = ~0x03;
cout << "w = " << w << endl;通过以下计算可以得到结果:
00000011 - 0x03
11111100 - ~0x03 b
0000000000000011 - 0x03
1111111111111100 - ~0x03 w
同&(与)运算符一起使用,可以使任意位置0。算法如下:将第四位置0BYTE b = 50;
cout << "b = " << b << endl;
BYTE c = b & ~0x10;
cout << "c = " << c << endl;通过以下计算可以得到结果:
00110010 - b
& 11101111 - ~0x10
----------
00100010 - result
>>和<< 右移和左移运算符
>>(右移)运算符和<<(左移)运算符将数据右移或左移若干位。>>右移运算从高位往低位移,<<左移运算从低位往高位移。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
位段
位段是位运算中比较令人感兴趣的部分。使用位段可以在一个字节,字或双字内设置小型结构。例如:要记录日期,要求尽可能少的使用内存,则可以采用如下的结构申明:struct date_struct {
BYTE day : 5, // 1 to 31
month : 4, // 1 to 12
year : 14; // 0 to 9999
} date;
在上面的例子中,日占据了最低的5位,月份占据了接下来的4位,年份为接下来的14位。则整个日期储存在三个字节的23位中。第二十四位被忽略。如果使用整形申明则将占据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 --+
如上所述,date类型使用的位段结构。这里使用的是BYTE。一BYTE为8位,使用的时候,编译器将申请一个BYTE来储存。如果结构超过8位,编译器将再申请一个BYTE,直到足够用来储存结构。如果使用字或双字,编译器将总共申请32位用来储存结构。
怎样申明位段?首先申明位段变量,跟着冒号,然后是分配给变量的位数;每位段用逗号分隔,最后用分号表示申明结束。
完成结构申明后,则可以通过存取标记方便的使用结构,同时也可以使用地址操作符使用结构的地址对结构进行操作。如下:
date.day = 12;
dateptr = &date;
dateptr->year = 1852;