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。
另,負數按補碼形式參加按位與運算。
按位與&比較實用的例子:
- 比如我們經常要用的是否被2整除,一般都寫成
if(n % 2 == 0)
if((n&1) == 0)
- 按位與運算可以取出一個數中指定位。例如:要取出整數84從左邊算起的第3、4、5、7、8位,只要執行84 & 59,因為84對應的二進位制為
01010100
,59對應的二進位制為00111011
,01010100 & 00111011= 00010000可知84從左邊算起的第3、4、5、7、8位分別是0、1、0、0、0。 - 清零。如果想將一個單元清零,使其全部二進位制位為0,只要與一個各位都為零的數值相與,結果為零。
或運算
參加運算的兩個物件,按二進位制位進行“或”運算。
運算規則:0|0=0; 0|1=1; 1|0=1; 1|1=1;
即 :參加運算的兩個物件只要有一個為1,其值為1。
例如:3|5 即 00000011 | 0000 0101 = 00000111 因此,3|5的值得7。
另,負數按補碼形式參加按位或運算。
按位或 (|) 比較實用的例子
-
可以用一個unsigned int 來儲存多個布林值。比如一個檔案有讀許可權,寫許可權,執行許可權。看起來要記錄3個布林值。我們可以用一個unsigned int也可以完成任務。
-
一個數r來表示讀許可權,它只更改個位來記錄讀許可權的布林值00000001 (表示有讀許可權)00000000 (表示沒有讀許可權)
-
一個數w表示寫許可權,它只用二進位制的倒數第二位來記錄布林值00000010 (表示有寫許可權)00000000 (表示沒有寫許可權)
-
一個數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)) |
最後一個會在樹狀陣列中用到