位操作是一種很底層的操作二進(jìn)制數(shù)據(jù)的方法,雖然比較難掌握,但是有時候卻有更高的效率和難以名狀的優(yōu)雅感。而且,在面試或者筆試中,考察基本的位操作應(yīng)用越老越普遍,所以掌握位操作的基本操作和應(yīng)用很有必要。
我們先從基本的位操作概念和基礎(chǔ)談起,并介紹其在程序中的用處比較多的應(yīng)用,最后根據(jù)幾道常用的算法題來總結(jié)升華。
什么是位操作
我們都知道數(shù)據(jù)在計算機(jī)存儲的形式是二進(jìn)制數(shù)據(jù),位操作就是一種在二進(jìn)制層面操作數(shù)據(jù)的方法,位操作直接操作0,1構(gòu)成的二進(jìn)制數(shù)據(jù)。
基本的位操作
基本的位操作有六種,分別是 ** 與 或 非 異或 左移 右移 **
符號 | 描述 | 運算規(guī)則 |
---|---|---|
& | 與 | 兩個位都為1時,結(jié)果才為1 |
或 | 兩個位都為0時,結(jié)果才為0 | |
^ | 異或 | 兩個位相同時,結(jié)果為1,不相同為0 |
~ | 取反 | 0變1,1變0 |
<< | 左移 | 各二進(jìn)位全部左移若干位,高位丟棄,低位補(bǔ)0 |
>> | 右移 | 各二進(jìn)位全部左移若干位,高位丟棄,高位補(bǔ)符號位,或者補(bǔ)零,根據(jù)不同編譯器 |
- ** 首先,必須明確位操作只能對整數(shù)進(jìn)行操作 **
- 在jdk中,java右移是進(jìn)行算術(shù)右移操作
- ** 位操作的優(yōu)先級很低,所以最好用括號 **
public class Byte {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a = 13, b = -13;
System.out.println(a>>2);
System.out.println(b>>2);
}
}
上述代碼的輸出結(jié)果:
下面我們就分析一下為什么會輸出這個結(jié)果:
- 首先對于13,我們寫出他的二進(jìn)制:0000 1101
- 右移兩位: 0000 0011,由于jdk中的右移是算術(shù)右移,所以高位補(bǔ)00,結(jié)果為3
- 對于-13,二進(jìn)制代碼: 1111 0011
- 右移兩位,高位補(bǔ)符號位,1111 1100,結(jié)果是-4
位操作的常用技巧
位操作經(jīng)常用于一些小操作,由于他只能操作整形數(shù),所以用途有限,但是一些常用的小技巧是非常值得掌握的,判斷奇偶,交換兩數(shù),交換符號,求絕對值等。下面我們就將一一介紹。
判斷奇偶
奇偶的區(qū)別體現(xiàn)在二進(jìn)制上,就是末尾是0,1 顯然當(dāng)末尾為0時,是偶數(shù),當(dāng)末尾為1是最后一位奇數(shù)。所以判斷奇偶的方法是:
if (a & 1 == 0)
為偶數(shù)
else
為奇數(shù)
一個小的測試程序:
for(int i=0;i<1000;i++)
{
if((i & 1) == 0)
System.out.println(i);
}
上面這個程序?qū)敵鏊?000以內(nèi)的偶數(shù)
交換兩數(shù)
利用位操作交換兩個數(shù)的好處是不用第三個temp變量(局限是只能交換整數(shù)變量)
if (a != b)
{
a ^= b;
b ^= a;
a ^= b;
}
分析一下交換是怎么產(chǎn)生的:
首先 a^=b 即a=(a^b);
b^=a 即b=b(ab),由于運算滿足交換律,b(ab)=bb^a。一個數(shù)和自己異或肯定是0,因為自己肯定是等于自己的啦,那么一個數(shù)和0異或的話,1和0異或還是1,0和0異或還是0,所以顯然一個數(shù)和0異或之后當(dāng)然還是自己本身。所以此時,b被賦值為a。
最后一步,a^=b 就是a=ab,由于前面二步可知a=(ab),b=a,所以a=ab即a=(ab)^a。故a會被賦上b的值。
變換符號
變換符號顯然很簡單,根據(jù)類似補(bǔ)碼,取反加一就可以了。
int SignReversal(int a)
{
return ~a + 1;
}
求絕對值
求絕對值就是在變換符號的基礎(chǔ)上實現(xiàn)的,我們只要先判斷是否為負(fù)數(shù),若是負(fù)數(shù),就變換符號,不是,就直接返回。
判斷正負(fù)可以直接判斷其符號位,右移31位,取到符號位,判斷正負(fù)
int my_abs(int a)
{
int i = a >> 31;
return i == 0 ? a : (~a + 1);
}
對于任何數(shù),與0異或都會保持不變,與-1即0xFFFFFFFF異或就相當(dāng)于取反。因此,a與i異或后再減i(因為i為0或-1,所以減i即是要么加0要么加1)也可以得到絕對值。所以可以對上面代碼優(yōu)化下:
int my_abs(int a)
{
int i = a >> 31;
return ((a ^ i) - i);
}
位操作的應(yīng)用,常見的算法題
位操作實現(xiàn)A+B的操作是常見的算法題。
lintcode上就有一道容易題是這樣。
class Solution {
/*
* param a: The first integer
* param b: The second integer
* return: The sum of a and b
*/
public int aplusb(int a, int b) {
// write your code here, try to do it without arithmetic operators.
if(a==0)return b;
if(b==0)return a;
int x1 = a^b;
int x2 = (a&b)<<1;
return aplusb(x1,x2);
}
};
上述代碼就實現(xiàn)了不用+操作符,利用位操作實現(xiàn)兩個數(shù)的相加操作。
現(xiàn)在我們來講解位操作實現(xiàn)兩個數(shù)相加的原理
首先,十進(jìn)制中,我們知道,7+8,不進(jìn)位和是5,進(jìn)位是1,然后我們可以根據(jù)不進(jìn)位和和進(jìn)位5+1*10算出最后的結(jié)果15。
類似二進(jìn)制也可以采取這種方法
比如
a = 3,b = 6
a : 0011
b : 0110
不進(jìn)位和: 0101 也就是5
進(jìn)位:0010 也就是2
所以a+b變成5 + (2<<1)
5 0101
2<<1 0100
不進(jìn)位和 0001 = 1
進(jìn)位 0100 = 4
因此 a + b就變成了1 + 4 << 1
然后有
1 0001
4<<1 1000
不進(jìn)位和 1001 = 9
進(jìn)位 0000 = 0
當(dāng)時進(jìn)位為0時,不進(jìn)位和為9即a + b之和。
可以發(fā)現(xiàn)上述是一個遞歸的過程,所以也就不難寫出代碼了。求兩個數(shù)的不進(jìn)位和實際上就是將兩個數(shù)異或操作即可。
小結(jié)
我們先從六種基本的位操作入手,然后介紹了位操作的常用技巧,判斷奇偶,求絕對值,交換符號,交換兩個數(shù)。最后根據(jù)常用的算法題中,利用位操作實現(xiàn)兩個數(shù)相加。基本總結(jié)了位操作簡單的應(yīng)用。
位操作當(dāng)然還有更豐富復(fù)雜的應(yīng)用,需要我們在學(xué)習(xí)過程中不斷總結(jié)。