1. 程式人生 > 其它 >C++中的位運算

C++中的位運算

位運算子

符號 含義
& 按位與
| 按位或
^ 按位異或
~ 取反
<< 左移
>> 右移

運算規則

與運算

“a&b”是指將參加運算的兩個整數a和b,按二進位制位進行“與”運算。

運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:兩位同時為“1”,結果才為“1”,否則為0
例如:3&5 即 0000 0011& 0000 0101 = 0000 0001 因此,3&5的值得1。
另,負數按補碼形式參加按位與運算。

按位與&比較實用的例子:

  1. 比如我們經常要用的是否被2整除,一般都寫成if(n % 2 == 0)
    可以換成if((n&1) == 0)
  2. 按位與運算可以取出一個數中指定位。例如:要取出整數84從左邊算起的第3、4、5、7、8位,只要執行84 & 59,因為84對應的二進位制為01010100,59對應的二進位制為0011101101010100 & 00111011= 00010000可知84從左邊算起的第3、4、5、7、8位分別是0、1、0、0、0。
  3. 清零。如果想將一個單元清零,使其全部二進位制位為0,只要與一個各位都為零的數值相與,結果為零。

或運算

參加運算的兩個物件,按二進位制位進行“或”運算。
運算規則:0|0=0; 0|1=1; 1|0=1; 1|1=1;
即 :參加運算的兩個物件只要有一個為1,其值為1。
例如:3|5 即 00000011 | 0000 0101 = 00000111 因此,3|5的值得7。 
另,負數按補碼形式參加按位或運算。

按位或 (|) 比較實用的例子

  1. 可以用一個unsigned int 來儲存多個布林值。比如一個檔案有讀許可權,寫許可權,執行許可權。看起來要記錄3個布林值。我們可以用一個unsigned int也可以完成任務。

  2. 一個數r來表示讀許可權,它只更改個位來記錄讀許可權的布林值00000001 (表示有讀許可權)00000000 (表示沒有讀許可權)

  3. 一個數w表示寫許可權,它只用二進位制的倒數第二位來記錄布林值00000010 (表示有寫許可權)00000000 (表示沒有寫許可權)

  4. 一個數x表示執行許可權,它只用倒數第三位來記錄布林值00000100 (表示有執行許可權)00000000 (表示沒有執行許可權)

那麼一個檔案同時沒有3種許可權就是~r | ~ w | ~ x 即為 00000000,就是0


只有讀的許可權就是r | ~w | ~x 即為 00000001,就是1
只有寫的許可權就是~r | w | ~x 即為 00000010,就是2
一個檔案同時有3種許可權就是r | w | x 即為 00000111,就是7

異或運算

參加運算的兩個資料,按二進位制位進行“異或”運算。
運算規則:0 ^ 0=0; 0 ^ 1=1; 1^ 0=1; 1^1=0;
即:參加運算的兩個物件,如果兩個相應位為“異”(值不同),則該位結果為1,否則為0。

下面重點說一下按位異或,異或 其實就是不進位加法,如1+1=0,,0+0=0,1+0=1。
異或的幾條性質:
1、交換律:a ^ b=b ^ a
2、結合律:(a ^ b) ^ c == a^ (b ^ c)

“異或運算”的特殊作用:
(1)使特定位翻轉: 例:X=10101110,使X低4位翻轉,用X ^ 0000 1111 = 1010 0001即可得到。
(2)與0相異或,保留原值 ,10101110^ 00000000 = 1010 1110。
(3)對於任何數x都有――自反性:x^ x=0,x^ 0=x 例如:A^B ^ B = A
(4)交換二個數:a =a ^ b; b = b ^ a; a = a ^ b;

按位異或應用舉例1:
給出 n 個整數,n 為奇數,其中有且僅有一個數出現了奇數次,其餘的數都出現了偶數次。用線性時間複雜度、常數空間複雜度找出出現了奇數次的那個數。
【輸入樣例】
9
3 3 7 2 4 2 5 5 4
【輸出樣例】
7

#include<bits/stdc++.h>
using namespace std;
int main()
{   int i,n,m,a;
    cin>>n;
    cin>>a;
    for(int i = 2; i <= n; i++) 
    {cin>>m; a^=m;   }
    cout<<a<<endl;
}

按位異或 應用舉例2:
1-1000放在含有1001個元素的陣列中,只有唯一的一個元素值重複,其它均只出現
一次。每個陣列元素只能訪問一次,設計一個演算法,將它找出來;不用輔助儲存空
間,能否設計一個演算法實現?
解法一、顯然已經有人提出了一個比較精彩的解法,將所有數加起來,減去1+2+...+1000的和。
這個演算法已經足夠完美了,相信出題者的標準答案也就是這個演算法,唯一的問題是,如果數列過大,則可能會導致溢位。
解法二、異或就沒有這個問題,並且效能更好。
將所有的數全部異或,得到的結果與123...1000的結果進行異或,得到的結果就是重複數。

#include<bits/stdc++.h>
using namespace std;
int main()
{  int i,n,a[11]={1,2,5,3,4,5,6,7,8,9,10};
   n=a[0]^a[1];
  for(i=2;i<=10;i++)n=n^a[i]; 
  for(i=1;i<=10;i++)n=n^i;
  cout<<n;
}

按位異或 應用舉例3:
一系列數中,除兩個數外其他數字都出現過兩次,求這兩個數字,並且按照從小到大的順序輸出.例如 2 2 1 1 3 4.最後輸出的就是3 和4

#include<bits/stdc++.h>
using namespace std;
int a[1000];
int main()
{       int n;
        scanf("%d", &n);
        int x = 0;
        for(int i = 1; i <= n; i++) {      scanf("%d", &a[i]); x ^= a[i];    }
        int num1 = 0, num2 = 0;
        int tmp = 1;
        while(!(tmp & x)) tmp <<= 1;
        cout<<tmp<<endl;
        for(int i = 1; i <= n; i++) {
            if(tmp & a[i]) num1 ^= a[i];
            else num2 ^= a[i];
        }
        printf("%d %d\n", min(num1, num2), max(num1, num2));
    return 0;
}

取反

按位取反運算子(~)是指將整數的各個二進位制位都取反,即1變為0,0變為1。

例如,~9=-10,因為9(00001001)所有位取反即為(11110110),這個數最高位是1,所以是補碼。補碼還原成反碼(反碼等於補碼減1)得到(11110101),再還原為原碼(反碼到原碼最高位不變,其它各位取反)等於(10001010), 十進位制為-10。

左移

左移運算子是用來將一個數的各二進位制位左移若干位,移動的位數由右運算元指定(右運算元必須是非負值),其右邊空出的位用0填補,高位左移溢位則捨棄該高位。

在高位沒有1的情況下,左移1位相當於該數乘以2,左移2位相當於該數乘以2*2=4,15<<2=60,即乘了4。
但此結論只適用於該數左移時被溢位捨棄的高位中不包含1的情況。

例如:143<<2 結果為60 因為143轉換為進製為10001111,左移2得00111100 ,結果為60。

右移

左移運算子是用來將一個數的各二進位制位左移若干位,移動的位數由右運算元指定(右運算元必須是非負值),其右邊空出的位用0填補,高位左移溢位則捨棄該高位。

在高位沒有1的情況下,左移1位相當於該數乘以2,左移2位相當於該數乘以2*2=4,15<<2=60,即乘了4。
但此結論只適用於該數左移時被溢位捨棄的高位中不包含1的情況。

例如:143<<2 結果為60 因為143轉換為進製為10001111,左移2得00111100 ,結果為60。

不同長度的資料進行位運算

如果兩個不同長度的資料進行位運算時,系統會將二者按右端對齊,然後進行位運算。

以“與”運算為例說明如下:如果一個4個位元組的資料與一個2個位元組資料進行“與”運算,右端對齊後,左邊不足的位依下面三種情況補足:

(1)如果整型資料為正數,左邊補16個0。

(2)如果整型資料為負數,左邊補16個1。

(3)如果整形資料為無符號數,左邊也補16個0。

常見操作

去掉最後一位 101101->10110 x>>1
在最後加一個0 101101->1011010 x<<1
在最後加一個1 101101->1011011 (x<<1)+1
把最後一位變成1 101100->101101 x | 1
把最後一位變成0 101101->101100 (x|1) - 1
最後一位取反 101101->101100 x ^ 1
把右數第K位變成1 101001->101101,k=3 x|(1<<(k-1))
把右數第K位變成0 101101->101101,k=3 x & ~(1<<(k-1))
右數第k位取反 101001->101101,k=3 x ^ (1<<(k-1))
取末三位 1101101->101 x &7
取末k位 1101101->1101,k=5 x & (1<<k-1)
取右數第k位 1101101->1,k=4 x >> (k-1)&1
把末k位變成1 101001->101111,k=4 x|(1<<k-1)
末k位取反 101001->100110,k=4 x^(1<<k-1)
把右邊連續的1變成0 100101111->100100000 x&(x+1)
把右起第一個0變成1 100101111->100111111 x|(x+1)
把右邊連續的0變成1 11011000->11011111 x|(x-1)
取右邊連續的1 100101111->1111 (x^(x+1))>>1
去掉右起第一個1的左邊 100101000->1000 x&(x^(x-1))

最後一個會在樹狀陣列中用到