1. 程式人生 > >C/C++的位運算子操作

C/C++的位運算子操作

 C/C++支援比較低階的位運算,在是眾人皆知的了。每本C/C++的教科書都會說到這部分的內容,不過都很簡略,我想會有很多人不知道位運算用在什麼地方。這個帖子就簡略說說位運算的用處,更進一步的用法要大家自己去體會。而主要說的是操作標誌值方面。
 考慮一個事物、一個系統、或者一個程式可能會出現一種或者幾種狀態。為了在不同的狀態下,作出不同的行為,你可以設立一些標誌值,再根據標誌值來做判斷。比如C++的檔案流,你就可以設定一些標誌值,ios::app, ios::ate, ios::binary, ios::in, ios::out, ios::trunc,並且可以將它用|組合起來建立一個恰當的檔案流。你可能會將這些標誌值定義為bool型別,不過這樣要是設定的標誌值一多,就會很浪費空間。


而假如定義一個整型數值,unsigned int flags; 在現在的系統,flags應該是32位, 用1,2,3....32將位進行編號,我們可以進行這樣的判斷, 當位1取1時,表示用讀方式開啟檔案,當位2取1時,表示用寫方式開啟檔案,當位3取1時,用二進位制方式開啟檔案....因為flags有32位,就可以設定32個不同的狀態值,也相當於32個bool型別。這樣一方面省了空間, 另一方面也多了個好處,就是如前面所說的,可以將標誌值組合起來。
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


好啦,上面有點不清不楚的。下面看看到底怎麼操作這些標誌值。
設想C++的類ios這樣定義, 其實沒有這個類,只有ios_basic類,typedef basic_ios<char> ios;

class ios
{
public:
enum app 0x0001, ate 0x0002, binary 0x0004,
in 0x0008, out 0x0010, trunc 0x0020 };
....
private:
unsigned int flags;
};

注意上面enum語句中,每一個數值只有1位是1,其餘是0,這個很重要,你可以將它化成2進位制看看。

現在將flags相應的位設定為1, 可以這樣做 flags |= app。這個等於flags = flags | app, 為什麼呢? app只有1位是1,其餘是0,因為0 | 1 = 0, 0 | 0 = 0, 這樣0對應的位是不變的。而1 | 1 = 1, 1 | 0 = 1, 1對應的位不論原來是什麼狀態,都一定為1。如果想要將幾個位都設定為1,可以這樣做 flags |= (app | ate | binary)。因為每個enum常數各有一位為1, 與運算之後就有3位為1,就如上面的分析,就可以將那3位都設定為1, 其餘位不變。這個就是標誌可以組合起來用的原因。也可以用+組合起來,原因在於(下面的數字是2進位制)0001 + 0010 + 0100 = 0111 跟與運算結果一樣。不過不提倡用+, 考慮(app | ate | binary)要是我不小心寫多了個標誌值,(app | ate | ate | binary)結果還是正確的,如果用+的話,就會產生進位,結果就會錯誤。通常我們不知道原先已經組合了多少個標誌值了,用或運算會安全。

現在將flags對應的位設定為0, 可以這樣做 flags &= ~app。相當於 flags flags (~app). app取反之後,只有1位是0,其餘是1,做與運算之後,1對應的位並不會改變,0對應的為不管原來是1是0,都肯定為0,這樣就將對應的位設定了0。同樣同時設定幾個標誌位可以這樣做,flags &= ~(app ate binary)。

現在將flags對應的位,如果是1就變成0,如果是0就變成1,可以這樣做 flags ^= app。同時設定幾個標誌位可以寫成 flags ^= (app ate binary)。不再做分析了,不然就太羅嗦了。不過也給大家一個例子,你查查Ascii表,會發現對應的大小寫字母是相差倒數第6位,可以用這樣的函式統一的將大寫變成小寫,小寫變成大寫。
void xchgUppLow(string& letters)
{
const unsigned int mask (1<<5);

for (size_t i=0; i<letters.length(); i++)
letters[i] ^= mask;
}
前提是輸入的string一定要全是字母, 而要想是操作字母,可以在原來基礎上加個判斷。

     好啦,上面已經可以設定flags的對應位值了,要是判斷呢?可以這樣寫 if (flags & app) 這樣可以判斷對應的位值是否為1, 因為C/C++語言中非0就真。app只有一位是1,其餘是0,如果, flags的對應位也是0,在與操作下就得到結果0,反之非0,這樣就可以判斷標誌位了。

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
上面關於標誌值的操作就介紹完畢。其實在C++中已經有了個bitset了,沒有必要去自己進行低階的位運算,上面的四個操作在bitset中分別叫做set, reset, flip, test。不過在C中,這樣的程式碼還很常見, 反正知道多點也沒有壞處。

用 windows API 程式設計,你也經常會碰到這樣的標誌值,要互相組合,可以用|, 也可以用+(只是建議用|,理由上面說了). 它的標誌值也是這樣定義的,不過用#define
#define WS_BORDER 0x0001
#define WS_CAPTION 0x0002
......
當初我就是想不明白為什麼可以用|或者用+來組合,現在知道了。

(注:上面出現的數字是我自己作的,到底實際怎麼定義其實沒有關係,只要保證只有一位是1,其餘是0就可以的了. 因為程式設計的時候用的是常量值,沒有人這樣笨去直接用數值的)

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
其實,位運算還有很多用處。比如移位相當於乘除2的冪數(不過通常編譯器也將乘除2的冪數優化成彙編的移位指令,所以沒有必要不要這樣賣弄了。彙編的移位指令有兩組,分別針對有符號和無符號的, 我猜想在C/C++的同一移位運算針對有符號整數和無符號整數的不同,會根據情況編譯成不同的彙編移位指令,不過沒有去證實), 其實移位更用得多的地方是去構造一個掩碼, 比如上面的mask (1<<5);

還有&運算,有時候可以用來求餘數。比如 value (1<<4 1) 這相當於將value的高位全變成0了,效果等於 value 8. 

還有值得一提的是^運算,它有個很特殊的性質。比如 ^= B, 變成另一個數,跟著再執行A ^= B,又變回原來的數了,不信你可以列真值表或者化簡邏輯式看看。就因為這個性質,^有很多用途。比如加密,你將原文看成A, 用同一個B異或一次,就相當於加密,跟著在用B異或一次,相當於解密。不過這樣是很容易破解就是了。要是一個B不夠,還可以加個C, 比如A ^= B, ^= C, ^= C, ^= B, 恢復原狀。

下面一個小程式,用異或交換兩個數字。
int 3;
int 4;

^= y;
^= x;
^= y;

其實和止交換數字,連交換物件也可以的
template <typename T>
void swap(T& obj1, T& obj2)
{
const int sizeOfObj sizeof(T);
char* pt1 (char*)&obj1;
char* pt2 (char*)&obj2;

for (size_t i=0; i<sizeOfObj; i++)