說(shuō)說(shuō)異或運(yùn)算^和他的一個(gè)常用作用。
異或的運(yùn)算方法是一個(gè)二進(jìn)制運(yùn)算:
1^1=0
0^0=0
1^0=1
0^1=1
兩者相等為0,不等為1.
這樣我們發(fā)現(xiàn)交換兩個(gè)整數(shù)的值時(shí)可以不用第三個(gè)參數(shù)。
如a=11,b=9.以下是二進(jìn)制
a=a^b=1011^1001=0010;
b=b^a=1001^0010=1011;
a=a^b=0010^1011=1001;
這樣一來(lái)a=9,b=13了。
舉一個(gè)運(yùn)用, 按一個(gè)按鈕交換兩個(gè)mc的位置可以這樣。
mybt.onPress=function()
{
mc1._x=mc1._x^mc2._x;
mc2._x=mc2._x^mc1._x;
mc1._x=mc1._x^mc2._x;
//
mc1._y=mc1._y^mc2._y;
mc2._y=mc2._y^mc1._y;
mc1._y=mc1._y^mc2._y;
}
這樣就可以不通過(guò)監(jiān)時(shí)變量來(lái)傳遞了。
最后要聲明:只能用于整數(shù)。
整數(shù)在計(jì)算機(jī)中用二進(jìn)制的位來(lái)表示,C語(yǔ)言提供一些運(yùn)算符可以直接操作整數(shù)中的位,稱為位運(yùn)算,這些運(yùn)算符的操作數(shù)都必須是整型的。在以后的學(xué)習(xí)中你會(huì)發(fā)現(xiàn),有些信息利用整數(shù)中的某幾個(gè)位來(lái)存儲(chǔ),要訪問(wèn)這些位,僅僅有對(duì)整數(shù)的操作是不夠的,必須借助位運(yùn)算,例如第 2 節(jié) “Unicode和UTF-8” 介紹的UTF-8編碼就是如此,學(xué)完本節(jié)之后你應(yīng)該能自己寫出UTF-8的編碼和解碼程序。本節(jié)首先介紹各種位運(yùn)算符,然后介紹與位運(yùn)算有關(guān)的編程技巧。
在第 3 節(jié) “布爾代數(shù)” 講過(guò)邏輯與、或、非運(yùn)算,并列出了真值表,對(duì)于整數(shù)中的位也可以做與、或、非運(yùn)算,C語(yǔ)言提供了按位與(Bitwise AND)運(yùn)算符&、按位或(Bitwise OR)運(yùn)算符|和按位取反(Bitwise NOT)運(yùn)算符~,此外還有按位異或(Bitwise XOR)運(yùn)算符^,我們?cè)?a style="color: #336699; text-decoration: initial;">第 1 節(jié) “為什么計(jì)算機(jī)用二進(jìn)制計(jì)數(shù)” 講過(guò)異或運(yùn)算。下面用二進(jìn)制的形式舉幾個(gè)例子。
注 意,&、|、^運(yùn)算符都是要做Usual Arithmetic Conversion的(其中有一步是Integer Promotion),~運(yùn)算符也要做Integer Promotion,所以在C語(yǔ)言中其實(shí)并不存在8位整數(shù)的位運(yùn)算,操作數(shù)在做位運(yùn)算之前都至少被提升為int
型了,上面用8位整數(shù)舉例只是為了書寫方便。比如:
unsigned char c = 0xfc;
unsigned int i = ~c;
計(jì)算過(guò)程是這樣的:常量0xfc是int
型的,賦給c
要轉(zhuǎn)成unsigned char
,值不變;c
的十六進(jìn)制表示是fc,計(jì)算~c
時(shí)先提升為整型(000000fc)然后取反,最后結(jié)果是ffffff03。注意,如果把~c
看成是8位整數(shù)的取反,最后結(jié)果就得3了,這就錯(cuò)了。為了避免出錯(cuò),一是盡量避免不同類型之間的賦值,二是每一步計(jì)算都要按上一章講的類型轉(zhuǎn)換規(guī)則仔細(xì)檢查。
移位運(yùn)算符(Bitwise Shift)包括左移<<和右移>>。左移將一個(gè)整數(shù)的各二進(jìn)制位全部左移若干位,例如0xcfffffff3<<2得到0x3fffffcc:
最高兩位的11被移出去了,最低兩位又補(bǔ)了兩個(gè)0,其它位依次左移兩位。但要注意,移動(dòng)的位數(shù)必須小于左操作數(shù)的總位數(shù),比如上面的例子,左邊是unsigned int
型,如果左移的位數(shù)大于等于32位,則結(jié)果是Undefined。移位運(yùn)算符不同于+ - * / ==等運(yùn)算符,兩邊操作數(shù)的類型不要求一致,但兩邊操作數(shù)都要做Integer Promotion,整個(gè)表達(dá)式的類型和左操作數(shù)提升后的類型相同。
復(fù)習(xí)一下第 2 節(jié) “不同進(jìn)制之間的換算” 講過(guò)的知識(shí)可以得出結(jié)論,在一定的取值范圍內(nèi),將一個(gè)整數(shù)左移1位相當(dāng)于乘以2 。比如二進(jìn)制11(十進(jìn)制3)左移一位變成110,就是6,再左移一位變成1100,就是12。讀者可以自己驗(yàn)證這條規(guī)律對(duì)有符號(hào)數(shù)和無(wú)符號(hào)數(shù)都成立,對(duì)負(fù)數(shù)也成立。當(dāng)然,如果左移改變了最高位(符號(hào)位),那么結(jié)果肯定不是乘以2了,所以我加了個(gè)前提“在一定的取值范圍內(nèi) ”。由于計(jì)算機(jī)做移位比做乘法快得多,編譯器可以利用這一點(diǎn)做優(yōu)化,比如看到源代碼中有i * 8
,可以編譯成移位指令而不是乘法指令。
當(dāng)操作數(shù)是無(wú)符號(hào)數(shù)時(shí),右移運(yùn)算的規(guī)則和左移類似,例如0xcfffffff3>>2得到0x33fffffc:
最低兩位的11被移出去了,最高兩位又補(bǔ)了兩個(gè)0,其它位依次右移兩位。和左移類似,移動(dòng)的位數(shù)也必須小于左操作數(shù)的總位數(shù),否則結(jié)果是Undefined。在一定的取值范圍內(nèi),將一個(gè)整數(shù)右移1位相當(dāng)于除以2,小數(shù)部分截掉。
當(dāng)操作數(shù)是有符號(hào)數(shù)時(shí),右移運(yùn)算的規(guī)則比較復(fù)雜:
綜上所述,由于類型轉(zhuǎn)換和移位等問(wèn)題,用有符號(hào)數(shù)做位運(yùn)算是很不方便的,所以,建議只對(duì)無(wú)符號(hào)數(shù)做位運(yùn)算,以減少出錯(cuò)的可能 。
1、下面兩行printf
打印的結(jié)果有何不同?請(qǐng)讀者比較分析一下。%x
轉(zhuǎn)換說(shuō)明的含義詳見(jiàn)第 2.9 節(jié) “格式化I/O函數(shù)” 。
int i = 0xcffffff3;
printf("%x/n", 0xcffffff3>>2);
printf("%x/n", i>>2);
如果要對(duì)一個(gè)整數(shù)中的某些位進(jìn)行操作,怎樣表示這些位在整數(shù)中的位置呢?可以用掩碼(Mask)來(lái)表示。比如掩碼0x0000ff00表示對(duì)一個(gè)32位整數(shù)的8~15位進(jìn)行操作,舉例如下。
1、取出8~15位。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = (a & mask) >> 8; /* 0x00000056 */
這樣也可以達(dá)到同樣的效果:
b = (a >> 8) & ~(~0U << 8);
2、將8~15位清0。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a & ~mask; /* 0x12340078 */
3、將8~15位置1。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a | mask; /* 0x1234ff78 */
1、統(tǒng)計(jì)一個(gè)無(wú)符號(hào)整數(shù)的二進(jìn)制表示中1的個(gè)數(shù),函數(shù)原型是int countbit(unsigned int x);
。
2、用位操作實(shí)現(xiàn)無(wú)符號(hào)整數(shù)的乘法運(yùn)算,函數(shù)原型是unsigned int multiply(unsigned int x, unsigned int y);
。例如:(11011)2 ×(10010)2 =((11011)2 <<1)+((11011)2 <<4)。
3、對(duì)一個(gè)32位無(wú)符號(hào)整數(shù)做循環(huán)右移,函數(shù)原型是unsigned int rotate_right(unsigned int x, int n);
。所謂循環(huán)右移就是把低位移出去的部分再補(bǔ)到高位上去,例如rotate_right(0xdeadbeef, 8)
的值應(yīng)該是0xefdeadbe。
1、一個(gè)數(shù)和自己做異或的結(jié)果是0。如果需要一個(gè)常數(shù)0,x86平臺(tái)的編譯器可能會(huì)生成這樣的指令:xorl %eax, %eax
。不管eax
寄存器里的值原來(lái)是多少,做異或運(yùn)算都能得到0,這條指令比同樣效果的movl $0, %eax
指令快,直接對(duì)寄存器做位運(yùn)算比生成一個(gè)立即數(shù)再傳送到寄存器要快一些。
2、從異或的真值表可以看出,不管是0還是1,和0做異或保持原值不變,和1做異或得到原值的相反值。可以利用這個(gè)特性配合掩碼實(shí)現(xiàn)某些位的翻轉(zhuǎn),例如:
unsigned int a, b, mask = 1U << 6;
a = 0x12345678;
b = a ^ mask; /* flip the 6th bit */
3、如果a1 ^ a2 ^ a3 ^ ... ^ an 的結(jié)果是1,則表示a1 、a2 、a3 ...an 之中1的個(gè)數(shù)為奇數(shù)個(gè),否則為偶數(shù)個(gè)。這條性質(zhì)可用于奇偶校驗(yàn)(Parity Check),比如在串口通信過(guò)程中,每個(gè)字節(jié)的數(shù)據(jù)都計(jì)算一個(gè)校驗(yàn)位,數(shù)據(jù)和校驗(yàn)位一起發(fā)送出去,這樣接收方可以根據(jù)校驗(yàn)位粗略地判斷接收到的數(shù)據(jù)是否有誤。
4、x ^ x ^ y == y,因?yàn)閤 ^ x == 0,0 ^ y == y。這個(gè)性質(zhì)有什么用呢?我們來(lái)看這樣一個(gè)問(wèn)題:交換兩個(gè)變量的值,不得借助額外的存儲(chǔ)空間,所以就不能采用temp = a; a = b; b = temp;
的辦法了。利用位運(yùn)算可以這樣做交換:
a = a ^ b;
b = b ^ a;
a = a ^ b;
分析一下這個(gè)過(guò)程。為了避免混淆,把a(bǔ)和b的初值分別記為a0 和b0 。第一行,a = a0 ^ b0
;第二行,把a(bǔ)的新值代入,得到b = b0 ^ a0 ^ b0
,等號(hào)右邊的b0 相當(dāng)于上面公式中的x,a0 相當(dāng)于y,所以結(jié)果為a0 ;第三行,把a(bǔ)和b的新值代入,得到a = a0 ^ b0 ^ a0
,結(jié)果為b0 。注意這個(gè)過(guò)程不能把同一個(gè)變量自己跟自己交換,而利用中間變量temp
則可以交換。
1、請(qǐng)?jiān)诰W(wǎng)上查找有關(guān)RAID(Redundant Array of Independent Disks,獨(dú)立磁盤冗余陣列)的資料,理解其實(shí)現(xiàn)原理,其實(shí)就是利用了本節(jié)的性質(zhì)3和4。
2、交換兩個(gè)變量的值,不得借助額外的存儲(chǔ)空間,除了本節(jié)講的方法之外你還能想出什么方法?本節(jié)講的方法不能把同一個(gè)變量自己跟自己交換,你的方法有沒(méi)有什么局限性?
本文轉(zhuǎn)自:http://blog.csdn.net/yunyuehu/article/details/5408446#t1
posted on 2013-01-18 11:07
王海光 閱讀(930)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
C++