目錄
在這篇博客中,我將來討論與位操作符有關的內容。這篇文章中談到的位操作符有:
- OR(C#中使用“|”,VB.NET中使用Or)
- AND(C#中使用“&”,VB.NET中使用And)
- XOR(C#中使用“^”,VB.NET中使用Xor)
- NOT(C#中使用“~”,VB.NET中使用Not)
- 左移運算符(C#和VB.NET中都使用<<)
- 右移運算符(C#和VB.NET中都使用>>)
- 循環按位移動
- 循環左移(C#和VB.NET中沒有對應的運算符)
- 循環右移(C#和VB.NET中沒有對應的運算符)
位操作符一般用在數值類型上,它作用在數字二進制格式的每一位上(0和1),所以我們先要搞清楚十進制和二進制的相互轉換。這篇文章開頭我會給出一些(二進制-十進制)轉換示例,雖然都是以Byte類型進行說明的,但其他諸如Int32、Int16等數值類型轉換的原理是一樣的。
位操作符的使用不僅僅只在C#和VB.NET兩種語言中,本篇文章只以這兩種語言舉例。
這一節中我將介紹有關十進制與二進制相互轉換的內容。
假設我們有一個十進制數字783,我們可以使用下面的方法將其轉換成二進制:
除法: |
783 / 2 |
391 / 2 |
195 / 2 |
97 / 2 |
48 / 2 |
24 / 2 |
12 / 2 |
6 / 2 |
3 / 2 |
1 / 2 |
商: |
391 |
195 |
97 |
48 |
24 |
12 |
6 |
3 |
1 |
0 |
余數: |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
當商為0時,我們停止計算。現在我們從右往左拼接每一步得到的余數,我們會得到1100001111。
按照下面方式可以將一個負十進制數轉換為二進制(以-783為例):
- 先得到783的二進制:0000001100001111(前面空白補0)
- 按位取反得到:1111110011110000
- 然后加1得到:1111110011110001
- 那么,-783的二進制為1111110011110001
- 怎么確定得到的結果是一個負數呢?這主要依賴於數據類型。如果數據類型為Int16,那么第一位若為0,則為正數,否則為負數。如果數據類型是不帶符號的,比如UInt16,那么第一位數不代表符號,1111110011110000就是十進制的64752。
如果你有一個二進制數字0000000100010110(Int16),現將每個位的順序顛倒(你會得到0110100010000000),然后使用以下方法:
位b: |
0 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
b * 2n |
0 * 20 |
1 * 21 |
1 * 22 |
0 * 23 |
1 * 24 |
0 * 25 |
0 * 26 |
0 * 27 |
1 * 28 |
0 * 29 |
0 * 210 |
0 * 211 |
0 * 212 |
0 * 213 |
0 * 214 |
0 * 215 |
結果: |
0 |
2 |
4 |
0 |
16 |
0 |
0 |
0 |
256 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
最后將每一步得到的結果相加:
按照下面方式可以將一個負二進制數值轉換成十進制(以1111111111010011為例):
- 將原數按位取反得到:0000000000101100
- 將取反后的結果轉換成十進制:44
- 將44加1得到45
- 將45變為負數:-45
- 最后,負二進制數值1111111111010011的十進制格式為-45
假設現在有兩個Byte類型的數38和53,那么我們先將它們轉換成二進制格式:
按照下表的方式:
A |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
B |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
A | B (A Or B) |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
1 |
如果兩個數是Int16類型的,那么它們就有可能是負數。一個負數和一個正數按位或運算后得到的結果還是負數(第一位肯定是1),因此,-15|378(VB.NET中-15 Or 378)的結果為-5。
C#和VB.NET中的按位或運算符的使用,參見下面代碼:
[C#]
[VB.NET]
通過使用FlagsAttribute,你可以將枚舉類型的每個值都當作二進制中的位(1和0),當然在定義枚舉類型的時候有要求,即每個枚舉值必須按照1、2、4、8(2的N次方)這樣的規律初始化。
[C#]
[VB.NET]
現在你可以使用按位或運算符來操作枚舉類型:
[C#]
[VB.NET]
假設有兩個數76和231,我們現將它們轉換成二進制:
然后按照下表計算:
A |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
B |
1 |
1 |
1 |
0 |
0 |
1 |
1 |
1 |
A & B (A And B) |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
僅僅當A和B都為負時,A&B(VB.NET中的A And B)的結果才為負,其他情況下結果都為正(只有A和B的第一位都為1時,結果的第一位才為1)。
C#和VB.NET中的按位與運算符的使用,參見下圖:
[C#]
[VB.NET]
按位或OR運算符不同於按位異或XOR運算符。如果你使用按位或OR,那么1|1(VB.NET中的1 Or 1)的結果為1,但是如果你使用按位異或XOR,那么1^1(VB.NET中的1 Xor 1)的結果為0。僅僅在1^0或者0^1時,結果才為1。
假設你有兩個數值138和43,那么現將它們轉換為二進制格式:
然后按照下表:
A |
1 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
B |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
A ^ B (A Xor B) |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
C#和VB.NET中的按位異或運算符的使用,參見下面代碼:
[C#]
[VB.NET]
使用XOR運算符可以交換兩個變量值,並且不需要中間臨時變量做輔助:
[C#]
[VB.NET]
在XOR運算符的幫助下,你可以給一個文本加密。遍歷文本的每個字符,然后使用XOR運算符c ^ k(VB.NET中的c Xor k)生成新的字符。其中k就是一個整數值。
[C#]
[VB.NET]
最終輸出結果為zFG]♫G]♫O♫CK]]OIK。這種方式加密非常容易被破解,所以最好不要使用單一的字符(比如k),我們可以使用一串文本:
[C#]
[VB.NET]
最終的輸出為m_☻\ D+♫.↓Z♫\SL?Ka。現在破解這個加密算法相對來講要復雜一些,但是這種方式還不是很保險,如果別人知道了你的key(代碼中的k字符串),那么破解起來相當簡單。因此,不要使用XOR這種方式作為加密的單一算法,如果你對安全、加密感興趣,你可以結合其他的一些加密方式,將XOR運算符應用到其中,作為整個加密過程的一部分。
按位非操作符NOT將會改變二進制中每位的值,0變為1,1變為0。如果一個數值有符號,那么整數經過運算后會變成負數,負數經過變換后會變為正數。如果數值沒有符號,那么永遠都為正(0除外)。假設你有一個數值52(二進制00110100,Byte類型,無符號),那么~52(VB.NET中的Not 52)的計算方式為:
A |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
0 |
~A |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
1 |
將11001011轉換成十進制為203,所以~52(Byte類型)的值為203。
C#和VB.NET中按位非運算符的使用,參見下面代碼:
[C#]
[VB.NET]
x<<n表示將X的二進制格式中的每位向左移動n個位置,右邊空出來的位置補0。
如圖所示,每位均向左移動1個位置,右邊空出來的位置補0。所以154<<1等於52。154<<n的值參見下表:
154 << 0 (= 154) |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
154 << 1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
0 |
154 << 2 |
0 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
154 << 3 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
154 << 4 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
154 << 5 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
154 << 6 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
154 << 7 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
154 << 8 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
C#和VB.NET中左移運算符的使用,參見下面代碼:
[C#]
[VB.NET]
1<<n的值為(2的n次方),但是使用這種方式計算2的冪要比使用Math.Pow更快:
[C#]
[VB.NET]
x>>n表示將x的二進制格式的每位均向右移動n個位置,左邊空出來的位置補0(與左移相反)。
如上圖所示,每位均向右移動1個位置。所以155>>1的值為77。注意如果為負數,那么它的符號會被隱藏掉。
下表顯示的是計算155>>n的值:
155 >> 0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
155 >> 1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
155 >> 2 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
155 >> 3 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
155 >> 4 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
155 >> 5 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
155 >> 6 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
155 >> 7 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
155 >> 8 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
C#和VB.NET中的右移運算符的使用,參見下面代碼:
[C#]
[VB.NET]
x>>n的值等於x/(2的n次方),比如8>>2的值為8/(2的2次方),也就是8/4。
[C#]
[VB.NET]
當然,這種方式的計算速度也要高於8/Math.Pow(2,2);
[C#]
[VB.NET]
循環按位左移會將數值的二進制格式中的每位均向左移動1個位置,然后將移出來的數值(1或0)替補到右邊空白處。
上圖顯示了將154循環按位向左移動1位,它的值等於154<<1|154>>7。循環按位左移得到的結果可以歸納為:a<<n|a>>(b-n)。b為數值的位數,如果數值為Byte類型,那么最后的結果為a<<n|a>>(8-n),如果數值為Int32類型,那么b為32,最后的結果為a<<n|a>>(32-n)。
C#和VB.NET中循環按位左移的使用,可以參見下面:
[C#]
[VB.NET]
循環按位右移會將數值的二進制格式的每位均向右移動1個位置,然后將移出來的數值(1或0)替補到左邊空白處。
如上圖所示,將155循環按位右移1個位置,最后它的值等於155>>1|155<<7。循環按位右移得到的結果可以歸納為:a>>n|a<<(b-n)。其中b為數值位數。如果數值為Byte類型,那么結果為a>>n|a<<(8-n),如果數值為Int32類型,那么得到的結果為a>>n|a<<(32-n)。
C#和VB.NET中循環按位右移的使用,可以參見下面代碼:
[C#]
[VB.NET]
譯者注:在使用位操作符時,一定要先確定被操作的數值是什么類型,占多少位,同一個數值,數據類型不同,最后得到的結果不一樣。原文中,對於任何一個數值(比如52),都在強調它是Byte類型還是Int16類型。