對於更多緊湊的資料,C 程式可以用獨立的位或多個組合在一起的位來儲存資訊。檔案存取許可就是一個常見的應用案例。位運算子允許對一個位元組或更大的資料單位中獨立的位做處理:可以清除、設定,或者倒置任何位或多個位。也可以將一個整數的位元型樣(bit pattern)向右或向左移動。
整數型別的位元型樣由一隊按位元置從右到左編號的位組成,位置編號從 0 開始,這是最低有效位(least significant bit)。例如,考慮字元值'*',它的 ASCII 編碼為 42,相當於二進位制的 101010:
位元型樣 0 0 1 0 1 0 1 0
位位置 7 6 5 4 3 2 1 0
在本例中,值 101010 被表示成一個 8 位的位元組內容,因此前面多兩個 0。
布林位運算子
表 1 中列舉的運算子可以對運算元的每個位進行布林運算。這種二元運算子把兩個不同運算元內相同位置的位關聯起來。被設定的位(也就是值為 1 的位)被解釋為 true,被清除的位(也就是值為 0 的位)被解釋為 false。
除布林運算子 AND、OR 和 NOT 以外,也有位互斥或運算子(exclusive-OR,XOR)。這些都在表 1 進行了列舉。
表1 布林位運算子
運算子 |
意義 |
範例 |
對於每個位位置的結果(1=設定,0=清除) |
& |
位 AND |
x&y |
如果 x 和 y 都為 1,則得到 1;如果 x 或 y 任何一個為 0,或都為0,則得到 0 |
| |
位 OR |
x|y |
如果 x 或 y 為 1,或都為 1,則得到 1;如果 x 和 y 都為 0,則得到 0 |
^ |
位 XOR |
x^y |
如果 x 或 y 的值不同,則得到 1;如果兩個值相同,則得到 0 |
~ |
位 NOT(I的二補數) |
~x |
如果 x 為 0,則得到 1,如果 x 是 1,則得到 0 |
位運算子的運算元必須是整數型別,並且遵循尋常算術轉換(usualarithmetic conversion)。轉換後獲得的運算元通用型別就是整個計算結果的型別。表 2 展示了這些運算子的效果。
表2 位運算子的效果
表示式(或宣告) |
位元型樣 |
int a=6; |
0···00110 |
int b=11; |
0···01011 |
a&b |
0···00010 |
a|b |
0···01111 |
a^b |
0···01101 |
~a |
1···11001 |
可以將一個整數 a 的特定位清除,做法是將整數 a 和另一個整數進行位 AND 運算,其中,另一個整數在需要清除的位為 0,其他位則為 1,並位 AND 運算,其中,另一個整數在需要清除的位為 0,其他位則為 1,並將 AND 運算的結果賦值給整數 a。
該另一個整數,即位 AND 運算的第二個運算元,被設定為 1 的位置(稱為位掩碼),這些位置經過位 AND 運算,不會改變第一個運算元對應位置的值。例如,一個整數與一個位掩碼 0xFF 進行位 AND 運算後,將保留最低位置的 8 個位,而會清除其他所有位的值:
a &= OxFF; // 相當於:a = a & OxFF;
在該範例中,複合賦值運算子 &= 也會執行 & 運算。複合賦值運算子與其他二元位運算子具有類似的執行方式,這裡不再贅述。
位運算子也
可以用來生成位掩碼,以供以後的位運算使用。例如,在位元型樣 0x20 中,只有位5被設定。因此表示式 ~0x20 會生成一個只有位 5 沒有被設定的位掩碼:
a &= ~0x20; // 清除a中的位5
位掩碼 ~0x20 比 0xFFFFFFDF 更受歡迎,因為它的可移植性更好:結果不會受到機器字大小的影響(同時也更方便人閱讀)。
也可以使用運算子 |(OR)和 ^(XOR)來設定或清除特定位,下面是一個範例:
int mask = OxC;
a |= mask; // 設定a的位2和位3
a ^= mask; // 求反a的位2和位3
第二個轉換使用相同的位掩碼,它會將第一次轉換的結果再反轉一次。換句話說,b^mask^mask 會得到原來 b 的值。這個操作可以用於交換兩個整數的值,而不需要使用第三個臨時變數:
a ^= b; // 等效於 a = a ^ b;
b ^= a; // 將a原來的值賦值給b
a ^= b; // 將b原來的值賦值給a
本例中的前兩個表示式等同於 b=b^(a^b)或 b=(a^b)^b。其結果等同於 b=a,副作用是 a 的值也被修改了,其修改後的值為 a^b。在這時,第三個表示式具有如下副作用 a=(a^b)^a 或 a=b(使用 a 和 b 的原始值)。
移位運算子
移位運算子將左運算元的位元型樣移動數個位置,至於移動幾個位置,由右運算元指定。它們如表 3 列舉。
表3 移位運算子
運算子 |
意義 |
範例 |
結果 |
<< |
向左移位 |
x<<y |
x 的每個位向左移動 y 個位 |
>> |
向右移位 |
x>>y |
x 的每個位向右移動 y 個位 |
移位運算子的運算元必須是整數。在實際移位元運算之前,兩個運算元都要進行整數提升(promotion)。右邊運算元不可以為負值,並且必須少於左邊運算元在整數提升之後的位長。如果不符合這些條件,程式執行結果將無法確定。
移位運算結果的型別等於左運算元在整數提升後的型別。下面範例的移位表示式具有 unsigned long 型別。
unsigned long n = 0xB, // 位元型樣: 0 ... 0 0 0 1 0 1 1
result = 0;
result = n << 2; // 0 ... 0 1 0 1 1 0 0
result = n >> 2; // 0 ... 0 0 0 0 0 1 0
在向左移位運算時,右邊多出來的位用 0 來填充。移動超出左邊邊界的位則直接拋棄。向左移動 y 個位置,就等同於將左運算元乘以 2^{y}:如果左運算元 x 是無符號型別,那麼表示式 x<<y 的結果等於表示式 x×2^{y} 的值。因此,在前面的例子,n<<2 的值為 n×4,也就是 44。
在向右位移運算時,如果左運算元是無符號型別,或者左運算元是帶符號型別但為非負值,則左邊多出來的位用 0 來填充。在這種情況下,表示式 x>>y 的結果等效於表示式 x/2^{y} 的值。如果左運算元是負值,那麼由編譯器決定用於填充至左邊多出來的位的內容,可能是 0,也可能是符號位。
// 函數setBit()
// 設定掩碼m中p位置的位。
// 使用定義在limits.h中的CHAR_BIT,儲存一個位元組內的位的數目。
// 返回值: 完成位設定的新掩碼,其中p位置已設定好
// 如果p不是有效的位置,則返回原始掩碼。
unsigned int setBit( unsigned int mask, unsigned int p )
{
if ( p >= CHAR_BIT * sizeof(int) )
return mask;
else
return mask | (1<<p);
}
移位運算子的優先順序比算術運算子的優先順序更低,但相對於比較運算子以及其他的位元運算運算子,具有更高的優先順序。上例表示式 mask|(1<<p)中的括號必要性不大,主要是讓程式程式碼更容易閱讀。