C語言中的位運算:
位運算,即對數據的二進制形式按位進行運算操作,c++中有多種位運算操作:
由於位運算是直接對內存中二進制數據進行操作,不需要進行轉化,因此效率很高,速度比+-*/等算數運算更快
C語言中 位運算速度 > +-速度 > */速度 > %速度
合理利用位運算操作可以一定程度上提高程序運行速度,從而避免TLE
壹.左移/右移:
<< 二進制左移(SHL)運算符:
將一個運算對象的各二進制位全部左移若干位,右邊補0,超出對應類型范圍時左邊的位自動丟棄。
例:
printf("%d", 3 << 2);//結果為12
(3)10 = (11)2
*(3)10表示十進制數3,(11)2表示二進制數11
向左移動兩位后,右邊補0,得到 (1100)2 = (1*23+1*22+0*21+0*20)10 = 12
不難看出: (1100)2 = 十進制下 22*(1*21+1*20) = 22*(3)
以此類推: 3<<k = 2k * 3 ;
同理:n<<k = n* 2^k
因此:我們可以借助 1<<k便捷的計算2的k次方
#由於位運算操做是直接針對內存中的二進制編碼進行簡單操做,所以位運算的運算效率要高於加減(+ -)操作,更快於乘除(* /)操作,
1<<k,遠遠快於需要進行k此操作的函數: pow(2,k); 可以減少計算耗時。
在程序時間限制極為緊張時,也可以考慮把一些乘法操作改寫成加法操作與左移操作的組合,從而提高計算效率
如: x*10 = x<<3+x<<1;
>> 二進制右移(SHR)運算符:
二進制右移運算符。將一個數的各二進制位全部右移若干位,正數左補0,負數左補1,右邊丟棄。
例:
printf("%d", 5 >> 1);//結果為2
printf("%d", -5 >> 1);//結果為-3
(5)10 = (00000101)2
向右移動並舍棄一位,左邊補0得 (00000010)2 = (2)10
(-5)10 在計算機中以二進制補碼的形式表示為:(11111011)
整體向右移一位,左邊補1得:(11111101) = (-3)10
類比左移運算不難看出: n>>k 相當於 n/2^k 向下取整 (注意:對負數向下取整相當於對其絕對值向上取整)
(不建議對負數進行位運算操作)
貳.按位 與 或 取反 異或 運算:
& 按位與(AND)運算符:
按位與操作,按二進制位進行"與"運算,每一位的運算規則於邏輯 “與” && 類似,只有在參與運算兩數同時為1時結果為1
即:1與1=1,1與0=0,0與1=0,0與0=0
( 二者位數不一致時向右對齊,較小數左端補0,如: 010 & 111 = 010 )
例:
printf("%d", 7 & 5);//結果為5
(7)10=(111)2 (5)10=(101)2
第一位1&1=1,第二位1&0=0,第三位1&1=1
(111)2 & (101)2 = (101)2 = (5)10
#與運算遵循 交換律,結合律,對或運算的分配律
即 a&b = b&a a&(b&c) = (a&b)&c a&(b|c) = (a&b)|(a&c)
#不難看出,一切正數奇數的二進制最低位一定是1,偶數最低位一定是0
所以可以通過n&1判斷n的奇偶性,結果為1則為奇,為0則為偶
因為位運算比取余運算更高效,所以該表達式比n%2耗時更少
#利用&將數的二進制最低非0位變為0
int a = 6;
printf("%d\n", a & a - 1);
6=(110)2 6-1=(101)2 6&5 =(100)2=4
#利用&求數a的二進制中有多少個一 時間復雜度O(k) k為a中1的數量
int a = 6, k = 0;
while (a)
{
a = a & a - 1;
k++;
}
printf("%d\n", k);
每次循環都會將a的最低為1位改為0,並且k++,當所有位全為0時循環結束,可算出a的二進制表示中有多少個1
| 按位或(OR)運算符:
按位或運算符,按二進制位進行"或"運算,每一位的運算規則於邏輯 “或” || 類似,只有在參與運算兩數同時為0時結果為0
即:0或0=0,1或0=1,0或1=1,1或1=1
( 二者位數不一致時向右對齊,較小數左端補0,如: 010 | 111 = 111 )
例:
printf("%d", 6 | 9);//結果為15
(6)10=(110)2=(0110)2 (9)10=(1001)2
第一位1|0=1,第二位0|1=1,第三位1|0=1,第四位0|1=1
(110)2 | (1001)2 = (1111)2 = (15)10
#或運算遵循 交換律,結合律,對與運算的分配律
即 a|b = b|a a|(b|c) = (a|b)|c a|(b&c) = (a|b)&(a|c)
~ 按位取反(NOT)運算符:
將二進制位取反,0變為1,1變為0 (對於原碼補碼的符號位同樣生效)
printf("%d", ~3);//結果為-4
(3)10 = (00000011)2 按位取反得到 11111100 是二進制補碼形式 轉化為十進制得 -4
# ~x+1 = -1*x (利用補碼的性質)
^ 按位異或(XOR)運算符:
異或運算符,按二進制位進行"異或"運算。運算規則:當參與運算兩數相同時結果為0,不相同時結果為1
即:0^0 = 0 , 1^1 = 0, 0^1 = 1, 1^0 = 1.
( 二者位數不一致時向右對齊,較小數左端補0,如: 010 ^ 111 = 101 )
例:
printf("%d", 2 ^ 3);//結果為1
(2)10=(10)2 , (3)10=(11)2
第一位1^1=0,第二位1^0=1
(10)2 ^ (11)2 = (01)2 = (1)10
#異或遵循:
1、交換律: a^b=b^a
2、結合律: (a^b)^c = a^(b^c)
3、對於任何數x,都有x^x=0,x^0=x
4、自反性 a^b^b = a^0=a
#利用異或交換兩數(高效率)
a ^= b;
b ^= a;
a ^= b;
第一步:a ^= b ---> a = (a^b)
第二步:b ^= a ---> b = b^(a^b) ---> b = (b^b)^a = a
第三步:a ^= b ---> a = (a^b)^a = (a^a)^b = b
叄.位運算賦值運算符:
類似於 +=,-=操做,可以將位運算符於等號組合,形成計算並賦值的位運算賦值運算符
<<= | 左移且賦值運算符 | C <<= n 等同於 C = C << n |
>>= | 右移且賦值運算符 | C >>= n 等同於 C = C >> n |
&= | 按位與且賦值運算符 | C &= n 等同於 C = C & n |
^= | 按位異或且賦值運算符 | C ^= n 等同於 C = C ^ n |
|= | 按位或且賦值運算符 | C |= n 等同於 C = C | n |
肆.運算符優先級:
1.括號 | () |
2.按位取反 | ~ |
3.乘除取余 | * / % |
4.加減 | + - |
5.左右移 | << >> |
6.關系運算 | < <= > >= |
7.相等 | == != |
8.按位與 | & |
9.按位異或 | ^ |
10.按位或 | | |
11.邏輯與 | && |
12.邏輯或 | || |
13.三目運算符 | ?: |
14.賦值 | = += -= *= /= %=>>= <<= &= ^= |= |
#可以發現除了~以外,其他位運算符的運算優先級均較低,大致上優先級 算數運算 > 位運算 > 邏輯運算
因此當出現位運算和算數運算(+-*/)相混合的表達式時,應當多加括號以避免運算順序錯誤
#不建議背誦此表,只需有大致印象即可,當不確定優先級時,可以通過多加括號解決
# & ^ | 優先級低於<<,因此在c++中可能出現如下錯誤
cout << 1 ^ 2 << endl;//編譯錯誤
因為 ^ 優先級低於<< ,所以編譯器會嘗試先執行輸出操作,后進行運算,顯然計算機是無法執行的,所以編譯時編譯器會報錯,加上括號即可
cout << (1 ^ 2) << endl;