java 位操作


1、位操作運算符的種類:&(與)、|(或)、~(取反)、^(異或)、<<(左移)、>>(右移)、>>>(無符號右移)。

2、位運算符操作不會短路。

3、位運算符操作的是補碼,所以~后正負號會發生變化。

4、位運算符只能用於整型。

5、反碼、補碼是相對於有符號數而言的,且不改變符號位。無符號數沒有反碼、補碼。

其中移位運算符需要注意的地方:

三個移位運算符的相同點:當移位的位數超出數值的位數則會取模后再移位。

<<(左移):左移所操作數字的補碼,二進制補碼整體左移(包括符號位),溢出則舍棄,低位補0。這意味着正數在左移的過程中是可以變成負數的。以Java中int型作為測試,Java中int型為4個字節,所以移位的位數為0~31,用32作為測試。如下

public class Tests{
    public static void main(String[] args) {
        System.out.println(1<<32);  //結果為1
        System.out.println(8>>33);  //結果為4
        System.out.println(8>>>33);  //結果為4
System.out.println(1<<31);  //結果為-2147483648 } }

由運行結果運行結果可以看出,對32進行了模運算。

但是如果我們一次不進行過長的移位就不會進行模運算,做如下測試

public class Tests{
    public static void main(String[] args) {
        int x = 1<<31;
        int y = 1<<31;
        System.out.println(x);  //輸出為-2147483648
        x = x<<31;
        System.out.println(x);  //輸出為0
        y = y<<1;
        System.out.println(y);  //輸出為0
    }
}

有上面的測試可以驗證上述的點。左移操作是沒有符號位一說的,直接對補碼的整體進行操作。所以也就沒有無符號左移<<<這樣的運算符。

>>(右移或者說是 有符號右移):右移所操作數字的補碼,二進制補碼整體右移(包括符號位),舍棄右側多出部分,高位補符號位。即相當於正數高位補0,負數高位補1。這樣的操作對於負數來說,會讓二進制數中的1的個數持續增多。但是得到的值是正確的。以整數-1來說。作如下測試。

public class Tests{
    public static void main(String[] args) {
        int x=-1;
        for(int i=0; i<10; i++){
            System.out.println(x>>1);  //輸出永遠是-1;
        }
    }
}

在這個測試中-1所對應的原碼為 ,為方便查看簡單到8位,-1的原碼為1000 0001 補碼為1111 1111 所以無論移動多少次,其結果永遠是-1。進一步可以以-8作為一個測試。一下完整的32位的-8、-4、-2、-1的原碼和補碼

-8原碼:1000 0000 0000 0000 0000 0000 0000 1000

-8補碼:1111 1111 1111 1111 1111 1111 1111 1000

-4補碼:1111 1111 1111 1111 1111 1111 1111 1100

-2補碼:1111 1111 1111 1111 1111 1111 1111 1110

-1補碼:1111 1111 1111 1111 1111 1111 1111 1111

由上述的內容我們可以看出,-8右移一位則除2,補碼中的1會多一個。但結果正確。

>>>(無符號右移):右移做操作數字的補碼,高位補0,低位超出則舍棄。由於高位補零,所以一個負數一旦移位則會變成正數。測試如下

public class Tests{
    public static void main(String[] args) {
        System.out.println(-1>>>1);  //結果為2147483647
    }
}

 一下為一些運用

統計十進制數對應的二進制中1的個數(統計的是補碼)

public class Tests{
    public static void main(String[] args) {
        int sum = 0;
        int n = -8;
        do{
            sum += n&1;
        }while((n >>>= 1)!=0);
        System.out.println(sum);  //29  n取任何數
    }
}

還有一種

public class Tests{
    public static void main(String[] args) {
        int n = -8;
        int sum = 0;
        while(n != 0){
            sum++;
            n = n&(n-1);
        }
        System.out.println(sum);  //29  n取任何數
    }
}

下面這種方法對於正數很好理解,就是每次都取n-1的值,這樣就會影響最低位的1及其更低位的值,影響的效果為取反,而高位不會影響。每次的循環都會利用&運算將低位的1去除。例如0111 1000,減一的值為0111 0111,&運算后為0111 0000。在負數的情況下,我們可以直接看補碼的關系,1111 1000的補碼為1000 1000,減一后(注意是負數加相反數)1111 1001的補碼為1000 0111,直接看補碼間的關系可以看出其實就是去掉符號位后,其余的值當作正數做減一運算與正數情況相同。

負數在變位補碼時,只需要確定最低的一位1,比最低的1高的位依次求反(不包括最低位的1)。所以可以看作,在最低的一位1前的數會受影響。這里負數的減一即絕對值加1后,在變為補碼,相當於從最低位就影響補碼的值。這樣可以認為,n(第一個1的高位取反)n-1(全部取反,相對於n)這樣的&操作,會使n的最低位1及其更低位變成0。

異或交換元素值

public class Tests{
    public static void main(String[] args) {
        int a=2;
        int b=3;
        a = a^b;
        b = a^b;
        a = a^b;
        System.out.println(a+"   "+b);  //3  2
    }
}

快速冪

 快速冪的主要思想為,將一個數的冪的指數,表示為二進制的科學計數法形式。

於是我們可以利用a來計算a的平方,再利用a的平方計算a的四次方。於是可以重復利用上一次的結果。二進制是可以表示任何整數的。於是指數就被拆分。簡化理解為8421碼,當a的5次方就有,0101,於是就有a的0次方乘a的4次方可得到。

public class Tests{
    public static void main(String[] args) {
        int x = 2, n = 5;        //x的n次方
        int ans = 1;        //保存結果
        while(n!=0){
            if((n&1)==1){
                ans *= x;        //該8421碼對應的位是1,就乘進結果
            }
            x *= x;        //計算8421碼對應的基數,
            n>>=1;        //准備8421碼的下一位
        }
        System.out.println(ans);
    }
}

 遞歸實現

int pow(int m,int n){   //m^n
    if(n==1) return m;
    int temp=pow(m,n/2);
    return (n%2==0 ? 1 : m)*temp*temp;
}

 這個遞歸實現我是這樣想的,以a的11次方為例(想成一條11米的繩子)。我們每一層的任務是,判斷自己是不是在最后一層,如果不是就把自己的一半分到下一層,讓下一層計算,留在自己這一層的要么和下一層一樣大,要么就比下一層大一個。下一層計算結束,就把原來的自己還原回來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM