電腦中的位元運算

來源:互聯網
上載者:User

標籤:blog   http   ar   使用   for   sp   strong   檔案   資料   

位元運算是C/C++中的基本運算之一,即便是這樣,它對大多數程式員來說是一個比較陌生的運算——大多數程式員很少使用位元運算。本篇先簡要介紹基本的位元運算操作符及其用法(何時使用),然後介紹位元運算符的幾個典型應用:

(1)      三種不用臨時變數交換兩個整數的執行個體,並分析每個執行個體的優缺點

(2)      進位轉換,通過位元運算實現將十進位數按二進位和十六進位輸出,並得出一個通用的,用於將十進位按照2的n次方進位輸出的程式。

(3)      給出利用位元運算實現的計算整數的二進位表示中有多少個1的執行個體。

揭開位元運算的面紗

所 有資料在電腦底層都是按二進位儲存的,一個資料可以看做是一個有序的位集合。每一位只有兩種狀態:0或1。位元運算允許程式員操作資料的某一特定位,比如 將某位設定為1(或0),查詢某位的狀態(1,或0)。位元運算由位元運算操作符和運算元組成,不同的位元運算操作符定義了不同的位元運算,下面的表格是對每種位 運算操作符及其對應的位元運算和功能進行描述:

位元運算操作符

對應的位元運算

用法

功能描述

~

按位非

~expr

翻轉expr的每一個位:1變0,0變1

<< 

左移

expr<<n

將expr向左移動n位,移到外面的被丟棄,右邊的位補0,因此左移n位相當於乘以2n

>> 

右移

expr>>n

將expr向右移n位,移到外面的被丟棄,如果expr是無符號類型,則左邊補0,否則,左邊插入符號位的拷貝或者0(視具體實現而定)。

&

按位與

expr1&expr2

在每個位所在處,如果expr1和expr2都含有1,那麼結果該位為1,否則為0。

|

按位或

Expr1 | expr2

在每個位所在處,如果expr1和expr2都含有0,那麼結果該位為0,否則為1。

^

按位異或

Expr1 ^ expr2

在每個位所在處,如果expr1和expr2不相同,那麼結果該位為1,否則為0.

除了上面的基本位元運算操作符外,還有&=,^=,|=,<<=,>>=等組合符號,它們分別是:按位與賦值,按位異或賦值,按位或賦值,左移賦值,右移賦值。接下來介紹如何?位操作:

1.將expr的第n(n從0開始)位設定為1:        expr |= (1<<n);

2.將expr的第n(n從0開始)位設定為0:    expr &= (~(1<<n));

3.判斷expr的第n(n從0開始)位是否為1:bool b =expr & (1<<n);

4.翻轉expr的第n(n從0開始)位:expr ^=(1<<n);

注意

1.      C標準提供了bitset來進行各種位操作,可以在MSDN中輸入bitset瞭解相關內容,使用時需要包含標頭檔:#include”bitset”。

2.      位 運算只能用於操作有整數類型的數,比如說char,short,int,long等(包含signed 和unsigned),不能操作浮點數,比如float,double!std::bitset的建構函式的參數是unsigned long int,盡量不要對負數進行為操作,因為可移植性差,不同的系統平台對負數的右移操作定義不一樣(大多數平台規定高位補符號位,有些平台規定高位補0)。

位元運算應用執行個體1:不用任何中間變數,交換兩個整數

這個問題是比較經典的了,你可以很容易地在網上找到多種答案,我在這裡給出兩個方案:

方案1:用算術運算實現(一個不完美的方案)

該方案的思路簡單,實現代碼很短,如下:

view plainprint?
  1. Template<class T>  
  2. Void mySwap_1(T& a, T& b)  
  3. {  
  4.          a = a+b;  
  5.          b = a -b;  
  6.          a = a-b;  
  7. }  

 

簡單吧,但是我還要簡單說一下:第一句a=a+b;是用a儲存原來的a跟原的b的和;第二句b =a-b;使得原來的a的值被儲存到了b裡面;最後一句a=a-b;使得原來的b的值儲存到了a裡面。

我們說這個方法是不那麼完美的,原因在於算術運算可能會出現結果溢出的問題,假如a,b都非常大,那麼第一句a=a+b就會導致結果溢出,比如說原來的a= 2147483647,b =2,那麼a+b就為2147483649,這個數大於了最大的不帶正負號的整數2147483648,因此發生溢出,a中儲存的結果實際上是:-2147483647,但是讓人驚訝的是:雖然第一句程式得到的結果為-2147483647,後面兩句得到的結果卻是正確的,即能實現交換原始a,b的值,也就是說:只有第一句的結果是錯誤的,但最後的結果卻是正確的,這一點讓我很迷惑,至今還沒弄清楚緣由,再次向各位求教!

最後,談談這種方法相對於後面的方案2的優點:該方法可以用於交換兩個非整數(浮點數),而方案2基於位元運算,而對浮點數不能直接使用位元運算,因此方案2不能用於交換兩個浮點數!

方案2:用位元運算實現(較好的方案)

        該方案代碼與方案1及其相似,思路也不難,先看代碼,然後再看我囉嗦的剖析:

view plainprint?
  1. template<class T>  
  2. void mySwap_2(T& a,T& b)  
  3. {  
  4.     a = a^b;  
  5.     b = b^a;  
  6.     a = a^b;  
  7. }  

 

對於編程老手來說,這個交換函數並不陌生,但我相信這些編程老手之中有一部分人只記得這麼寫代碼,而不知道三句代碼為何這麼寫,事實上我最初也是這樣,因此一開始我就覺得短短3行代碼,讓我花費時間去理解分析,還不如直接記憶來得划算。事實上,直到今天我寫這篇文章時,我捨得消耗一點腦細胞來理解它,下面我嘗試著對上述三句代碼進行闡述,為了方便,假設資料類型為char,並且a = 5,b=3;那麼在記憶體中a,b儲存如下:

a:

0

0

0

0

0

1

0

1

 

b:

0

0

0

0

0

0

1

1

 

接下來詳細分析每一句:

首先來看第一句:a=a^b;執行該語句後a中儲存了a與b的差異位,也就是說如果原來的a和b的某一位不同,那麼就將a的該位置為1,因此a在記憶體中成了如的樣子,它說明a與b的第2,3個bit有差異:

a:

0

0

0

0

0

1

1

0

 

接著我們來看第二句:b=b^a;其意思是,將b中有差異的位翻轉,如此一來b中儲存的值其實就等於原來a中的值,記住當第二個語句執行完之後a仍然儲存了原來的a,b的差異資訊,而b則變成了原來的a!

最後我們來看第三句:a=a^b;由於異或運算滿足交換律,因此這一句等價於:a=b^a;記住這個語句賦值號右邊的b中已經儲存了原始的a值,而a中儲存了原始的a,b的差異,因此這一句的最終作用是將原始a中有差異的位翻轉(變成b)然後賦值給a,如此一來a中就儲存了原始的b值。

總結:上述三句中:第一句是記錄差異,第2,3句是翻轉,最終實現了不用任何中間變數就交換兩個變數的值。

分析:位元運算不考慮進位問題,因此不會有結果溢出的問題!但是由於不能對浮點數進行直接位元運算,因此該方法不能實現交換兩個浮點數!當然原題題目是交換兩個整數。

備忘:還有其他實現兩個數交換的方法,比如採用記憶體拷貝!由於不屬於位元運算範疇,這裡就不贅述了。

 

位元運算應用執行個體2:進位轉換

要求:分別實現十進位整數按二進位、十六進位輸出。

兩種方法實現按二進位輸出:

方法1:由於整數在電腦中是按二進位儲存的,我們只需要將其每個bit按順序列印出來即可,如果某位為1,則列印字元‘1’,否則列印字元‘0’。我給出的代碼如下:

view plainprint?
  1. voidprintBinary(int num)  
  2. {    
  3.     for(int i=0;i<32;i++)  
  4.     {  
  5.        cout<<((num>>(31-i))&1);  
  6.         //cout<<( (num &(1<<(31-i))) ==0? 0 : 1 );  
  7.     }  
  8. }  

 

其中被注釋掉的那個cout與沒注釋的cout有同樣的功能!這個函數的思路很簡單,就是從高到底逐位列印每個bit。我上面的代碼有一點不好的地方,那就是語句太複雜,一個cout語句幹了太多的事情,如果影響您的理解,那麼你可以增加幾個臨時變數,然後把它拆分成多個簡單語句。我這麼寫主要是考慮到篇幅的原因,因此程式段太占篇幅了。隨便說一句,編程時,語句力求簡單明了:一行唯寫一條語句,一條語句只幹一件事情!

方法二:利用bitset來實現

bitset是標準庫提供的一個類(不是容器),利用它就可以很方便地操作位,下面是用bitset來實現的程式:

view plainprint?
  1. voidprintBinary(int num)  
  2. {   
  3.    bitset<32> bits =bitset<32>((unsigned long)(num));  
  4.     for(int i=31;i>=0;i--)  
  5.     {  
  6.         cout<<(bits[i]==true? ‘1‘ : ‘0‘);  
  7.     }     
  8. }  

 

備忘:關於bitset重載了多個運算子,其中包含下標運算子:[],可以方便地取得某一個bit,看它是否為1。關於bitset的更多資訊請查閱msdn或者其他資料,你只要記住bitset是標準庫提供的,你可以隨時使用,不要忘記添加相應的標頭檔。

實現按16進位輸出:

同樣由於資料在記憶體中是按二進位儲存的,因此將整數按照16進位輸出我們可以如下做:從左向右,每4位bit一組,組合成一個十六進位數,一次輸出即可,其程式如下:

view plainprint?
  1. void printHex(int num)  
  2. {  
  3.     for(inti=28;i>=0;i-=4)  
  4.     {  
  5.         int temp =num>>i;  
  6.         temp =temp&15;  //15是掩碼!  
  7.         char ch;  
  8.        temp>9?(ch =‘A‘+temp-10):(ch = ‘0‘+temp);  
  9.        cout<<ch;  
  10.     }     
  11. }     

 

該 程式與上面的printBinary函數非常相似,要注意的是i每次變化4,最關鍵點在於語句temp=temp&15;由於是16進位,因此這裡用15做掩碼。我想有了printBinary做鋪墊,理解這個printHex並不難,這裡不贅述了。接下來我將對這兩個函數進行個小小的擴充:實現整數按2n(2的n次方)進位輸出!比如按8進位,32進位等。為了方便描述,我們限制1<=n<=6;並用字元’0’到’9’表示數字0到9,用字元A,B,……Z,a,b,……表示數字10到63。程式如下:

view plainprint?
  1. void print2powerN(int num,int N)  
  2. {  
  3.     for(inti=32-N;i>=0;i-=N)  
  4.     {  
  5.         int temp =num>>i;  
  6.         temp =temp&((1<<N)-1);  
  7.         char ch;  
  8.        if(temp<=9)  
  9.         {  
  10.             ch =‘0‘+temp;  
  11.         }  
  12.         elseif(temp<=35)  
  13.         {  
  14.             ch =‘A‘+temp-10;  
  15.         }  
  16.         else  
  17.         {  
  18.             ch = ‘a‘+temp - 36;  
  19.         }             
  20.        cout<<ch;  
  21.     }  
  22. }  

 

備忘:用位元運算也能實現十進位到任意進位的轉換,這個問題比較難,我暫時還沒弄透徹!

 

位元運算案例3:求整數的二進位表示中1的個數

問題描述:輸入一個整數N要求輸出其二進位表示中1的個數M,比如N=13,則M=3;

分析:該問題的求解方法不止一種,可以對二進位表示的每一位逐位掃描來實現,這種方法的複雜度是o(n)其中n是N的二進位表示的總位元。這裡介紹如何用位操作來求解,並且保證其複雜度低於o(n),事實上該方法的複雜度為o(m),其中m是N的二進位標識中1的個數!

思路:在講述具體實現時,來看這樣一個事實:n&(n-1)能實現將最低位的1翻轉!比如說n=108,其二進位表示為01101100,則n&(n-1)的結果是01101000。因此只要不停地翻轉n的二進位的最低位的1,每翻轉一次讓計數器+1,直到n等於0時,計數器中就記錄了n的二進位中1的位元,程式如下:

view plainprint?
    1. int count1Bits(long n)  
    2. {  
    3.     int count =0;  
    4.     while(n)  
    5.     {  
    6.         count++;  
    7.        n&=(n-1);  
    8.     }  
    9.     return count;  

電腦中的位元運算

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.