位運算,相比普通的**最大的優點就是其帶來的高效性,也因此可以常在底層原始碼中看見它們的蹤影。
首先還是先來回顧下位操作的基礎知識。(除非特別說明,否則以下都以 2 進製為例)
與運算子 "&" 是雙目運算子。只有對應的兩個二進位均為 1 時,結果位才為 1,否則為 0。例如:
9 & 5 = 00001001
& 00000101
= 00000001
= 1
或運算子 "|" 是雙目運算子。只要對應的兩個二進位有乙個為 1 時,結果位就為 1。例如:
9 | 5 = 00001001
| 00000101
= 00001101
= 13
非運算子 "~" 為單目運算子。其功能是對參與運算的各二進位求反。例如:
~ 9 = ~ 00001001
= 11110110
= -10
異或運算子 "^" 是雙目運算子。其功能是對參與運算的二進位相異或,即當兩二進位相異時,結果為 1,相同就為 0。例如:
9 ^ 5 = 00001001
^ 00000101
= 00001100
= 12
例 1例 2
x01100011
10010101
x << 4
00110000
01010000
x >> 4(邏輯右移)
00000110
00001001
x >> 4(算術右移)
00000110
11111001
左移動就是向左移動 k 位,丟棄最高的 k 位,並在右端補 k 個 0,也就是常說的當前值乘以 2 的 k 次方。
右移動的原理也是相同的,右移 k 位就是當前數除以 2 的 k 次方。唯一不同的是分為邏輯右移和算術右移。
邏輯右移就是無符號移位,右移幾位,就在左端補幾個 0。
算術右移動是有符號移位,和邏輯右移不同的是,算術右移是在左端補 k 個最高有效位的值,如此看著有些奇特,但對有符號整數資料的運算非常有用。我們知道有符號的數,首位位元組,是用來表示數字的正負(1 為負)。負數採用補碼形式來儲存,比如 - 26(11100110),算術右移 1 位之後 - 13(11110011)。如若不是補最高有效位的值 1 而是補作 0 的話,右移之後就變成正數了。
i 取反再與 i 相加,相當於把所有二進位制位設為 1,其十進位制結果為 - 1。
-~n == n + 1
,~n 為其取反,負號 '-' 再對其取反並加 1。
~-n == n - 1
,思路就是找到最低位的第乙個 1,對其取反並把該位後的所有位也取反,即01001000
變為01000111
。
思路就是取反並加 1,也即~n + 1
或者(n ^ -1) + 1
。
利用 ^ 運算子的性質,即得x = a ^ b ^ x
。
當 n 為 2 的冪時,(x + n - 1) & ~(n - 1)
會找到第乙個大於 x 的數,且它正好是 n 的整數倍。
/* version 1 */
intcount_1_bits
(int n)
return count;
}/* version 2 */
intcount_1_bits
(int n)
關於第二個版本,分析如下:(摘自 matrix67 - 位運算,並作稍微修改)
以十進位制數 211 為例,其二進位制為 11010011,
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <— 原數
+---+---+---+---+---+---+---+---+
| 1
0 | 0
1 | 0
0 | 1
0 | <— 第一次運算後
+-------+-------+-------+-------+
| 001
1 | 001
0 | <— 第二次運算後
+---------------+---------------+
| 000
0010
1 | <— 第三次運算後,得數為 5
+-------------------------------+
整個程式是乙個分治的思想。第一次我們把每相鄰的兩位加起來,得到每兩位裡 1 的個數,比如前兩位 10 就表示原數的前兩位有 2 個 1。第二次我們繼續兩兩相加,10+01=11,00+10=10,得到的結果是 00110010,它表示原數前 4 位有 3 個 1,末 4 位有 2 個 1。最後一次我們把 0011 和 0010 加起來,得到的就是整個二進位制中 1 的個數。
x = x ^ (x >> 1);
x = x ^ (x >> 2);
x = x ^ (x >> 4);
x = x ^ (x >> 8);
x = x ^ (x >> 16);
cout
<< (x & 1) << endl; // 輸出 1 為奇數
以下分析摘自 matrix67 - 位運算,並作稍微修改,
以十進位制數 1314520 為例,其二進位制為 0001 0100 0000 1110 1101 1000。
第一次異或操作的結果如下:
0001
0100
0000
1110
1101
1000
^ 0000
1010
0000
0111
0110
1100
= 0001
1110
0000
1001
1011
0100
得到的結果是乙個新的二進位制數,其中右起第 i 位上的數表示原數中第 i 和 i+1 位上有奇數個 1 還是偶數個 1。比如,最右邊那個 0 表示原數末兩位有偶數個 1,右起第 3 位上的 1 就表示原數的這個位置和前乙個位置中有奇數個 1。
對這個數進行第二次異或的結果如下:
0001
1110
0000
1001
1011
0100
^ 0000
0111
1000
0010
0110
1101
= 0001
1001
1000
1011
1101
1001
結果裡的每個 1 表示原數的該位置及其前面三個位置中共有奇數個 1,每個 0 就表示原數對應的四個位置上共偶數個 1。
一直做到第五次異或結束後,得到的二進位制數的最末位就表示整個 32 位數裡 1 的奇偶性。
/* 判斷是否是奇數 */
bool
is_odd
(int n)
/* 此方法對 a 和 b 相等的情況不適用 */
a ^= b;
b ^= a; // 相當於 b = b ^ ( a ^ b );
a ^= b;
/* 注意:以下的數字 31 是針對 int 大小為 32 而言 */
intabs
(int n)
其中n >> 31
取得 n 的正負號。
/* 注意:以下的數字 31 是針對 int 大小為 32 而言 */
intmax
(int a, int b)
如果a >= b
,(a - b) >> 31
為 0,否則為 - 1。
/* 若 x,y 都為 0,輸出真;若只有乙個為 0,不會報錯但執行結果是錯的,因為 0 沒有正負之分 */
bool
is_same_sign
(int x, int y)
bool
is_power_of_two
(int n)
如果是 2 的冪,n - 1
就是把 n 的二進位制的最低的那個 1 取反為 0,並把後面的 0 全部取反為 1。
/* 其中 m 為 2 的冪次方,並對 m 取餘 */
intmod
(int n, int m)
位運算總結
位運算是指按二進位制進行的運算。在系統軟體中,常常需要處理二進位制位的問題。c語言提供了6個位操作 運算子。這些運算子只能用於整型運算元,即只能用於帶符號或無符號的char,short,int與long型別。c語言提供的位運算子列表 運算子 含義 描述 按位與 如果兩個相應的二進位制位都為1,則該位...
位運算總結
位運算是指按二進位制進行的運算。在系統軟體中,常常需要處理二進位制位的問題。c語言提供了6個位操作 運算子。這些運算子只能用於整型運算元,即只能用於帶符號或無符號的char,short,int與long型別。c語言提供的位運算子列表 運算子 含義 描述 按位與 如果兩個相應的二進位制位都為1,則該位...
位運算總結
一 優先順序 高 低 算術運算子 關係運算子 賦值 二 移位運算 要點 1 它們都是雙目運算子,兩個運算分量都是整形,結果也是整形。2 左移 右邊空出的位上補0,左邊的位將從字頭擠掉,其值相當於乘2。3 右移 右邊的位被擠掉。對於左邊移出的空位,如果是正數則空位補0,若為負數,可能補0或補1,這取決...