| 導購 | 订阅 | 在线投稿
分享
 
 
 

C語言中的位運算

來源:互聯網  2008-06-01 01:54:54  評論

在計算機程序中,數據的位是可以操作的最小數據單位,理論上可以用「位運算」來完成所有的運算和操作。一般的位操作是用來控制硬件的,或者做數據變換使用,但是,靈活的位操作可以有效地提高程序運行的效率。C語言提供了位運算的功能, 這使得C語言也能像彙編語言一樣用來編寫系統程序。

位運算符C語言提供了六種位運算符:

& 按位與

按位或

^ 按位異或

~ 取反

<< 左移

>> 右移

1. 按位與運算 按位與運算符"&"是雙目運算符。其功能是參與運算的兩數各對應的二進位相與。只有對應的兩個二進位均爲1時,結果位才爲1 ,否則爲0。參與運算的數以補碼方式出現。

例如:9&5可寫算式如下: 00001001 (9的二進制補碼)&00000101 (5的二進制補碼)00000001 (1的二進制補碼)可見9&5=1。

按位與運算通常用來對某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 運算 ( 255 的二進制數爲0000000011111111)。

應用:

a. 清零特定位 (mask中特定位置0,其它位爲1,s=s&mask)

b. 取某數中指定位 (mask中特定位置1,其它位爲0,s=s&mask)

2. 按位或運算 按位或運算符「」是雙目運算符。其功能是參與運算的兩數各對應的二進位相或。只要對應的二個二進位有一個爲1時,結果位就爲1。參與運算的兩個數均以補碼出現。

例如:95可寫算式如下:

0000100100000101

00001101 (十進制爲13)可見95=13

應用:

常用來將源操作數某些位置1,其它位不變。 (mask中特定位置1,其它位爲0 s=smask)

3. 按位異或運算 按位異或運算符「^」是雙目運算符。其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果爲1。參與運算數仍以補碼出現,例如9^5可寫成算式如下:

00001001^00000101 00001100 (十進制爲12)

應用:

a. 使特定位的值取反 (mask中特定位置1,其它位爲0 s=s^mask)

b. 不引入第三變量,交換兩個變量的值 (設 a=a1,b=b1)

目 標 操 作 操作後狀態

a=a1^b1 a=a^b a=a1^b1,b=b1

b=a1^b1^b1 b=a^b a=a1^b1,b=a1

a=b1^a1^a1 a=a^b a=b1,b=a1

4. 求反運算 求反運算符~爲單目運算符,具有右結合性。 其功能是對參與運算的數的各二進位按位求反。例如~9的運算爲: ~(0000000000001001)結果爲:1111111111110110

5. 左移運算 左移運算符「<<」是雙目運算符。其功能把「<< 」左邊的運算數的各二進位全部左移若幹位,由「<<」右邊的數指定移動的位數, 高位丟棄,低位補0。 其值相當于乘2。例如: a<<4 指把a的各二進位向左移動4位。如a=00000011(十進制3),左移4位後爲00110000(十進制48)。

6. 右移運算 右移運算符「>>」是雙目運算符。其功能是把「>> 」左邊的運算數的各二進位全部右移若幹位,「>>」右邊的數指定移動的位數。其值相當于除2。

例如:設 a=15,a>>2表示把000001111右移爲00000011(十進制3)。對于左邊移出的空位,假如是正數則空位補0,若爲負數,可能補0或補1,這取決于所用的計算機系統。移入0的叫邏輯右移,移入1的叫算術右移,Turbo C采用邏輯右移。

main(){

unsigned a,b;

printf("input a number: ");

scanf("%d",&a);

b=a>>5;

b=b&15;

printf("a=%d b=%d ",a,b);

}

再看一例:

main(){

char a='a',b='b';

int p,c,d;

p=a;

p=(p<<8)b;

d=p&0xff;

c=(p&0xff00)>>8;

printf("a=%d b=%d c=%d d=%d ",a,b,c,d);

}

浮點數的存儲格式:

浮點數的存儲格式是符號+階碼(定點整數)+尾數(定點小數)

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

即1位符號位(0爲正,1爲負),8位指數位,23位尾數位

浮點數存儲前先轉化成2的k次方形式,即:

f = A1*2^k + A2*2^(k-1) + ... + Ak +... +An*2^(-m) (Ai = {0, 1}, A1 = 1)

如5.5=2^2 + 2^0 + 2^(-1)

其中的k就是指數,加127後組成8位指數位

5.5的指數位就是2+127 = 129 = 10000001

A2A3.....An就是尾數位,不足23位後補0

所以5.5 = 01000000101000000000000000000000 = 40A00000

所以,對浮點數*2、/2只要對8位符號位+、- 即可,但不是左移、右移

關于unsigned int 和 int 的在位運算上的不同,下面有個CU上的例子描述的很清楚:

[問題]:這個函數有什麽問題嗎?

/////////////////////////////////////////////////

/**

* 本函數將兩個16比特位的值連結成爲一個32比特位的值。

* 參數:sHighBits 高16位

* sLowBits 低16位

* 返回:32位值

**/

long CatenateBits16(short sHighBits, short sLowBits)

{

long lResult = 0; /* 32位值的臨時變量*/

/* 將第一個16位值放入32位值的高16位 */

lResult = sHighBits;

lResult <<= 16;

/* 清除32位值的低16位 */

lResult &= 0xFFFF0000;

/* 將第二個16位值放入32位值的低16位 */

lResult = (long)sLowBits;

return lResult;

}

/////////////////////////////////////////////////

[問題的發現]:

我們先看如下測試代碼:

/////////////////////////////////////////////////

int main()

{

short sHighBits1 = 0x7fff;

short sHighBits2 = 0x8f12;

unsigned short usHighBits3 = 0xff12;

short sLowBits1 = 0x7bcd;

long lResult = 0;

printf("[sHighBits1 + sLowBits1] ";

lResult = CatenateBits16(sHighBits1, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(sHighBits2, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(usHighBits3, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

}

/////////////////////////////////////////////////

運行結果爲:

[sHighBits1 + sLowBits1]

lResult = 7fff7bcd

lResult = 8f127bcd

lResult = ff127bcd

嗯,運行很正確嘛……于是我們就放心的在自己的程序中使用起這個函數來了。

可是忽然有一天,我們的一個程序無論如何結果都不對!經過n個小時的檢查和調試,最後終于追蹤到……CatenateBits16() !?它的返回值居然是錯的!!

「郁悶!」你說,「這個函數怎麽會有問題呢!?」

可是,更郁悶的還在後頭呢,因爲你把程序中的輸入量作爲參數,在一個簡單的main()裏面單步調試:

/////////////////////////////////////////////////

int main()

{

short sHighBits1 = 0x7FFF;

short sHighBits2 = 0x8F12;

unsigned short usHighBits3 = 0x8F12;

short sLowBits1 = 0x7BCD; //你實際使用的參數

short sLowBits2 = 0x8BCD; //你實際使用的參數

long lResult = 0;

printf("[sHighBits1 + sLowBits1] ";

lResult = CatenateBits16(sHighBits1, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(sHighBits2, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(usHighBits3, sLowBits1);

printf("lResult = %08x ", lResult, lResult);

printf(" [sHighBits1 + sLowBits2] ";

lResult = CatenateBits16(sHighBits1, sLowBits2);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(sHighBits2, sLowBits2);

printf("lResult = %08x ", lResult, lResult);

lResult = CatenateBits16(usHighBits3, sLowBits2);

printf("lResult = %08x ", lResult, lResult);

return 0;

}

/////////////////////////////////////////////////

發現結果竟然是:

[sHighBits1 + sLowBits1]

lResult = 7fff7bcd

lResult = 8f127bcd

lResult = 8f127bcd

[sHighBits1 + sLowBits2]

lResult = ffff8bcd //oops!

lResult = ffff8bcd //oops!

lResult = ffff8bcd //oops!

前一次還好好的,後一次就ffff了?X檔案?

[X檔案的真相]:

注重那兩個我們用來當作低16位值的sLowBits1和sLowBits2。

已知:

使用 sLowBits1 = 0x7bcd 時,函數返回正確的值;

使用 sLowBits2 = 0x8bcd 時,函數中發生X檔案。

那麽,sLowBits1與sLowBits2有什麽區別?

注重了,sLowBits1和sLowBits2都是short型(而不是unsigned short),所以在這裏,sLowBits1代表一個正數值,而sLowBits2卻代表了一個負數值(因爲8即是二進制1000,sLowBits2最高位是1)。

再看CatenateBits16()函數:

/////////////////////////////////////////////////

long CatenateBits16(short sHighBits, short sLowBits)

{

long lResult = 0; /* 32位值的臨時變量*/

/* 將第一個16位值放入32位值的高16位 */

lResult = sHighBits;

lResult <<= 16;

/* 清除32位值的低16位 */

lResult &= 0xFFFF0000;

/* 將第二個16位值放入32位值的低16位 */

lResult = (long)sLowBits; //注重這一句!!!!

return lResult;

}

/////////////////////////////////////////////////

假如我們在函數中用

printf("sLowBits = %04x ", sLowBits);

打印傳入的sLowBits值,會發現

sLowBits = 0x7bcd 時,打印結果爲

sLowBits = 7bcd

而sLowBits = 0x8bcd時,打印結果爲

sLowBits = ffff8bcd

是的,即使用%04x也打印出8位十六進制。

因此,我們看出來了:

當sLowBits = 0x8bcd時,函數中 "lResult = (long)sLowBits;" 這一句執行,會先將sLowBits轉換爲

0xffff8bcd

再與lResult做或運算。由于現在lResult的值爲 0xXXXX0000 (其中XXXX是任何值),所以顯然,無論sHighBits是什麽值,最後結果都會是

0xffff8bcd

而當sLowBits = 0x7bcd時,函數中 "lResult = (long)sLowBits;" 這一句執行,會先將sLowBits轉換爲

0x00007bcd

再與lResult做或運算。這樣做或運算出來的結果當然就是對的。

也就是說,CatenateBits16()在sLowBits的最高位爲0的時候表現正常,而在最高位爲1的時候出現偏差。

[教訓:在某些情況下作位運算和位處理的時候,考慮使用無符號數值——因爲這個時候往往不需要處理符號。即使你需要的有符號的數值,那麽也應該考慮自行在調用CatenateBits16()前後做轉換——究竟在位處理中,有符號數值相當詭異!]

下面這個CatenateBits16()版本應該會好一些:

/////////////////////////////////////////////////

unsigned long CatenateBits16(unsigned short sHighBits, unsigned short sLowBits)

{

long lResult = 0;

/* 將第一個16位值放入32位值的高16位 */

lResult = sHighBits;

lResult <<= 16;

/* 清除32位值的低16位 */

lResult &= 0xFFFF0000;

/* 將第二個16位值放入32位值的低16位 */

lResult = (long)sLowBits & 0x0000FFFF;

return lResult;

}

/////////////////////////////////////////////////

注重其中的 "lResult = (long)sLowBits & 0x0000FFFF;"。事實上,現在即使我們把CatenateBits16()函數的參數(非凡是sLowBits)聲明爲short,結果也會是對的。

假如有一天你把一只兔子扔給一只老虎,老虎把兔子吃了,第二天把一只老鼠扔給它,它又吃了,那麽說明第一天你看錯了:它本來就是一只貓。

  在計算機程序中,數據的位是可以操作的最小數據單位,理論上可以用「位運算」來完成所有的運算和操作。一般的位操作是用來控制硬件的,或者做數據變換使用,但是,靈活的位操作可以有效地提高程序運行的效率。C語言提供了位運算的功能, 這使得C語言也能像彙編語言一樣用來編寫系統程序。   位運算符C語言提供了六種位運算符:   & 按位與    按位或   ^ 按位異或   ~ 取反   << 左移   >> 右移   1. 按位與運算 按位與運算符"&"是雙目運算符。其功能是參與運算的兩數各對應的二進位相與。只有對應的兩個二進位均爲1時,結果位才爲1 ,否則爲0。參與運算的數以補碼方式出現。   例如:9&5可寫算式如下: 00001001 (9的二進制補碼)&00000101 (5的二進制補碼) 00000001 (1的二進制補碼)可見9&5=1。   按位與運算通常用來對某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 運算 ( 255 的二進制數爲0000000011111111)。 應用: a. 清零特定位 (mask中特定位置0,其它位爲1,s=s&mask) b. 取某數中指定位 (mask中特定位置1,其它位爲0,s=s&mask)   2. 按位或運算 按位或運算符「」是雙目運算符。其功能是參與運算的兩數各對應的二進位相或。只要對應的二個二進位有一個爲1時,結果位就爲1。參與運算的兩個數均以補碼出現。    例如:95可寫算式如下: 0000100100000101 00001101 (十進制爲13)可見95=13 應用: 常用來將源操作數某些位置1,其它位不變。 (mask中特定位置1,其它位爲0 s=smask)   3. 按位異或運算 按位異或運算符「^」是雙目運算符。其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果爲1。參與運算數仍以補碼出現,例如9^5可寫成算式如下: 00001001^00000101 00001100 (十進制爲12) 應用: a. 使特定位的值取反 (mask中特定位置1,其它位爲0 s=s^mask) b. 不引入第三變量,交換兩個變量的值 (設 a=a1,b=b1) 目 標 操 作 操作後狀態 a=a1^b1 a=a^b a=a1^b1,b=b1 b=a1^b1^b1 b=a^b a=a1^b1,b=a1 a=b1^a1^a1 a=a^b a=b1,b=a1   4. 求反運算 求反運算符~爲單目運算符,具有右結合性。 其功能是對參與運算的數的各二進位按位求反。例如~9的運算爲: ~(0000000000001001)結果爲:1111111111110110   5. 左移運算 左移運算符「<<」是雙目運算符。其功能把「<< 」左邊的運算數的各二進位全部左移若幹位,由「<<」右邊的數指定移動的位數, 高位丟棄,低位補0。 其值相當于乘2。例如: a<<4 指把a的各二進位向左移動4位。如a=00000011(十進制3),左移4位後爲00110000(十進制48)。 6. 右移運算 右移運算符「>>」是雙目運算符。其功能是把「>> 」左邊的運算數的各二進位全部右移若幹位,「>>」右邊的數指定移動的位數。其值相當于除2。   例如:設 a=15,a>>2 表示把000001111右移爲00000011(十進制3)。對于左邊移出的空位,假如是正數則空位補0,若爲負數,可能補0或補1,這取決于所用的計算機系統。移入0的叫邏輯右移,移入1的叫算術右移,Turbo C采用邏輯右移。 main(){  unsigned a,b;  printf("input a number: ");  scanf("%d",&a);  b=a>>5;  b=b&15;  printf("a=%d b=%d ",a,b); }   再看一例: main(){  char a='a',b='b';  int p,c,d;  p=a;  p=(p<<8)b;  d=p&0xff;  c=(p&0xff00)>>8;  printf("a=%d b=%d c=%d d=%d ",a,b,c,d); } 浮點數的存儲格式: 浮點數的存儲格式是符號+階碼(定點整數)+尾數(定點小數) SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM 即1位符號位(0爲正,1爲負),8位指數位,23位尾數位 浮點數存儲前先轉化成2的k次方形式,即: f = A1*2^k + A2*2^(k-1) + ... + Ak +... +An*2^(-m) (Ai = {0, 1}, A1 = 1) 如5.5=2^2 + 2^0 + 2^(-1) 其中的k就是指數,加127後組成8位指數位 5.5的指數位就是2+127 = 129 = 10000001 A2A3.....An就是尾數位,不足23位後補0 所以5.5 = 01000000101000000000000000000000 = 40A00000 所以,對浮點數*2、/2只要對8位符號位+、- 即可,但不是左移、右移 關于unsigned int 和 int 的在位運算上的不同,下面有個CU上的例子描述的很清楚: [問題]:這個函數有什麽問題嗎? ///////////////////////////////////////////////// /** * 本函數將兩個16比特位的值連結成爲一個32比特位的值。 * 參數:sHighBits 高16位 * sLowBits 低16位 * 返回:32位值 **/ long CatenateBits16(short sHighBits, short sLowBits) { long lResult = 0; /* 32位值的臨時變量*/ /* 將第一個16位值放入32位值的高16位 */ lResult = sHighBits; lResult <<= 16; /* 清除32位值的低16位 */ lResult &= 0xFFFF0000; /* 將第二個16位值放入32位值的低16位 */ lResult = (long)sLowBits; return lResult; } ///////////////////////////////////////////////// [問題的發現]: 我們先看如下測試代碼: ///////////////////////////////////////////////// int main() { short sHighBits1 = 0x7fff; short sHighBits2 = 0x8f12; unsigned short usHighBits3 = 0xff12; short sLowBits1 = 0x7bcd; long lResult = 0; printf("[sHighBits1 + sLowBits1] "; lResult = CatenateBits16(sHighBits1, sLowBits1); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(sHighBits2, sLowBits1); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(usHighBits3, sLowBits1); printf("lResult = %08x ", lResult, lResult); } ///////////////////////////////////////////////// 運行結果爲: [sHighBits1 + sLowBits1] lResult = 7fff7bcd lResult = 8f127bcd lResult = ff127bcd 嗯,運行很正確嘛……于是我們就放心的在自己的程序中使用起這個函數來了。 可是忽然有一天,我們的一個程序無論如何結果都不對!經過n個小時的檢查和調試,最後終于追蹤到……CatenateBits16() !?它的返回值居然是錯的!! 「郁悶!」你說,「這個函數怎麽會有問題呢!?」 可是,更郁悶的還在後頭呢,因爲你把程序中的輸入量作爲參數,在一個簡單的main()裏面單步調試: ///////////////////////////////////////////////// int main() { short sHighBits1 = 0x7FFF; short sHighBits2 = 0x8F12; unsigned short usHighBits3 = 0x8F12; short sLowBits1 = 0x7BCD; //你實際使用的參數 short sLowBits2 = 0x8BCD; //你實際使用的參數 long lResult = 0; printf("[sHighBits1 + sLowBits1] "; lResult = CatenateBits16(sHighBits1, sLowBits1); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(sHighBits2, sLowBits1); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(usHighBits3, sLowBits1); printf("lResult = %08x ", lResult, lResult); printf(" [sHighBits1 + sLowBits2] "; lResult = CatenateBits16(sHighBits1, sLowBits2); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(sHighBits2, sLowBits2); printf("lResult = %08x ", lResult, lResult); lResult = CatenateBits16(usHighBits3, sLowBits2); printf("lResult = %08x ", lResult, lResult); return 0; } ///////////////////////////////////////////////// 發現結果竟然是: [sHighBits1 + sLowBits1] lResult = 7fff7bcd lResult = 8f127bcd lResult = 8f127bcd [sHighBits1 + sLowBits2] lResult = ffff8bcd //oops! lResult = ffff8bcd //oops! lResult = ffff8bcd //oops! 前一次還好好的,後一次就ffff了?X檔案? [X檔案的真相]: 注重那兩個我們用來當作低16位值的sLowBits1和sLowBits2。 已知: 使用 sLowBits1 = 0x7bcd 時,函數返回正確的值; 使用 sLowBits2 = 0x8bcd 時,函數中發生X檔案。 那麽,sLowBits1與sLowBits2有什麽區別? 注重了,sLowBits1和sLowBits2都是short型(而不是unsigned short),所以在這裏,sLowBits1代表一個正數值,而sLowBits2卻代表了一個負數值(因爲8即是二進制1000,sLowBits2最高位是1)。 再看CatenateBits16()函數: ///////////////////////////////////////////////// long CatenateBits16(short sHighBits, short sLowBits) { long lResult = 0; /* 32位值的臨時變量*/ /* 將第一個16位值放入32位值的高16位 */ lResult = sHighBits; lResult <<= 16; /* 清除32位值的低16位 */ lResult &= 0xFFFF0000; /* 將第二個16位值放入32位值的低16位 */ lResult = (long)sLowBits; //注重這一句!!!! return lResult; } ///////////////////////////////////////////////// 假如我們在函數中用 printf("sLowBits = %04x ", sLowBits); 打印傳入的sLowBits值,會發現 sLowBits = 0x7bcd 時,打印結果爲 sLowBits = 7bcd 而sLowBits = 0x8bcd時,打印結果爲 sLowBits = ffff8bcd 是的,即使用%04x也打印出8位十六進制。 因此,我們看出來了: 當sLowBits = 0x8bcd時,函數中 "lResult = (long)sLowBits;" 這一句執行,會先將sLowBits轉換爲 0xffff8bcd 再與lResult做或運算。由于現在lResult的值爲 0xXXXX0000 (其中XXXX是任何值),所以顯然,無論sHighBits是什麽值,最後結果都會是 0xffff8bcd 而當sLowBits = 0x7bcd時,函數中 "lResult = (long)sLowBits;" 這一句執行,會先將sLowBits轉換爲 0x00007bcd 再與lResult做或運算。這樣做或運算出來的結果當然就是對的。 也就是說,CatenateBits16()在sLowBits的最高位爲0的時候表現正常,而在最高位爲1的時候出現偏差。 [教訓:在某些情況下作位運算和位處理的時候,考慮使用無符號數值——因爲這個時候往往不需要處理符號。即使你需要的有符號的數值,那麽也應該考慮自行在調用CatenateBits16()前後做轉換——究竟在位處理中,有符號數值相當詭異!] 下面這個CatenateBits16()版本應該會好一些: ///////////////////////////////////////////////// unsigned long CatenateBits16(unsigned short sHighBits, unsigned short sLowBits) { long lResult = 0; /* 將第一個16位值放入32位值的高16位 */ lResult = sHighBits; lResult <<= 16; /* 清除32位值的低16位 */ lResult &= 0xFFFF0000; /* 將第二個16位值放入32位值的低16位 */ lResult = (long)sLowBits & 0x0000FFFF; return lResult; } ///////////////////////////////////////////////// 注重其中的 "lResult = (long)sLowBits & 0x0000FFFF;"。事實上,現在即使我們把CatenateBits16()函數的參數(非凡是sLowBits)聲明爲short,結果也會是對的。 假如有一天你把一只兔子扔給一只老虎,老虎把兔子吃了,第二天把一只老鼠扔給它,它又吃了,那麽說明第一天你看錯了:它本來就是一只貓。
󰈣󰈤
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
王朝網路微信公眾號
微信掃碼關註本站公眾號 wangchaonetcn
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有