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. 

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

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

x ^= y;
y ^= x;
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++)
        {
                pt1[i] ^= pt2[i];
                pt2[i] ^= pt1[i];
                pt1[i] ^= pt2[i];
        }
}

還 有異或操作還可以用在圖象的光柵操作。我們知道,顏色也是用二進制來表示的,對顏色進行不同的位運算,就可以得到不同的光柵。因爲異或的特殊性質,我們用 異或操作的光柵畫了副圖,跟着再在原來的地方畫一次,那副圖就刷除了。這樣可以用來顯示動畫而不用保存原來的畫像信息。以前我寫過個雙人的貪食蛇,就用了 異或光柵。因爲背景色是白色的,也就是全1,作A ^ 1 = A, 所以用畫刷畫一次是畫了設定的顏色,再畫一次就恢復。最有趣的是兩蛇相交的時候,顏 色也會作異或疊加,產生一種新的顏色了,離開的時候也會自動恢復。
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
好 啦,夠長了,就停止吧。在最後再給大家一段代碼,是用來看看對象在內存中的位值的。可以看看。
string bitsOfUChar(unsigned char c)
{
        const int numOfBitsInUChar = 8;
        unsigned int mask = (1<<7);
        string result(8, '0');

        for (size_t i=0; i<numOfBitsInUChar; i++)
        {
                if ( mask & c)
                        result[i] = '1';

                mask >>= 1;
        }

        return result;
}

template <typename T>
string bitsInMemory(const T& obj)
{
        int sizeOfObj = sizeof(obj);
        unsigned char* pt = (unsigned char*)&obj;
        string result;

        for (size_t i=0; i<sizeOfObj; i++)
        {
                result += bitsOfUChar(pt[i]);
                result += ' ';
        }

        return result;
}

比 如bitsInMemory(12),會輸出00001100 00000000 00000000 00000000, 我就知道我自己的機器是小尾順 序的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章