前言
我注意到有些人對位運算感到困惑,是以我決定寫這篇簡單的教程來說明位運算如何操作。
位簡介
位,它是什麼?你可能會問。
簡單來說,位就是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,而第一和第零位置0
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。算法如下:将第四位置0
BYTE 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;