---
tags: C++教學筆記
---
# 位元運算
## 二進制
要學習位元運算,必須要先學會二進制的數字表示法,
二進制與我們常用的十進制看起來非常不一樣,但核心概念是相同的。
* **二進制與十進制**
二進制與十進制的「進」代表的是進位,而前面的數字代表的是遇到多少要進位,
十進制是遇到10就進位 ~~**`看吧!變兩位數了`**~~ ,二進制則是遇到2就進位,
所以其實也有八進制、十六進制等不同的進位方式。
* **數字的位數**
我們如果去觀察一個十進制數字,其實可以發現最右邊(個位數)其實代表的是$10^0$的數,往左一位(十位數)代表$10^1$的數,以此類推。
二進制其實也是如此,但是把10都改成2,
例如二進制的`1001` 代表的是 $1\times 2^3 + 0\times 2^2 + 0\times 2^1 + 1\times 2^0 = 9$ 。
---
## 位元
大家應該都知道,電腦儲存資料的方法只有用`0`和`1`,而我們將一個儲存`0`或`1`的單位稱為位元`Bit`,又將8個位元合稱為一個位元組`Byte`。
如果去看資料型態的表格,你可以知道一個整數的記憶體空間是`4 bytes`,也就是`4x8 = 32 bits`
其中是用二進制儲存的,而有`1 bit`要被用來代表正負號,所以實際可以儲存的範圍是 $-2^{31}$**~**$2^{31}-1$ 。

---
## 位元運算子
* **NOT運算子 `~`**
* 使用方法: `~n`
* 功能: 將一個數字內所有位元的`1`變成`0`,`0`變成`1`
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `n` | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 158 |
| `~n` | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | -159 |
* **AND運算子 `&`**
| | 1 | 0 |
| ----- | - | - |
| **1** | 1 | 0 |
| **0** | 0 | 0 |
* 使用方法: `a & b`
* 功能: 將兩個數字內的位元位置對齊做AND邏輯運算(請見上表)
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `a` | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 154 |
| `b` | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 57 |
| `a & b` | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 24 |
* **OR運算子 `|`**
| | 1 | 0 |
| ----- | - | - |
| **1** | 1 | 1 |
| **0** | 1 | 0 |
* 使用方法: `a | b`
* 功能: 將兩個數字內的位元位置對齊做OR邏輯運算(請見上表)
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `a` | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 154 |
| `b` | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 57 |
| `a \| b` | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 187 |
* **XOR運算子 `^`**
| | 1 | 0 |
| ----- | - | - |
| **1** | 0 | 1 |
| **0** | 1 | 0 |
* 使用方法: `a ^ b`
* 功能: 將兩個數字內的位元位置對齊做XOR邏輯運算(請見上表)
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `a` | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 154 |
| `b` | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 57 |
| `a ^ b` | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 163 |
* **右移運算子 `>>`**
* 使用方法: `a >> b`
* 功能: 將數字`a`內的位元向右推移`b`格 `(左邊正數補0,負數補1,右邊自動丟棄)`
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `a` | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 154 |
| `a >> 2` | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 38 |
* **左移運算子 `<<`**
* 使用方法: `a << b`
* 功能: 將數字`a`內的位元向左推移`b`格 `(右邊自動補0)`
| | $\cdots$ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 值 |
| - | :-: | - | - | - | - | - | - | - | - | :-: |
| `a` | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 26 |
| `a << 2` | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 104 |
> 其實右移運算子 `>>` 及左移運算子 `<<` 做的事情非常簡單,分別是將數值除以2的i次方(整數除法)以及乘以2的i次方而已,可以思考看看為什麼。
* **應用**
* 判斷奇偶數:
前面(三元運算子)有稍微帶到判斷奇偶數可以使用 `n & 1`,不過沒有解釋為什麼,
現在講到位元運算子就有辦法解釋了,你可以自己先想想看:
幾個重要的點:
`1的二進制是 00000001` 、`只要是奇數,最後一個位元必定是1` 、`AND運算子`
因為`1`除了$2^0$之外,其他都是0,所以AND的結果必定會是`0`。
當我們對任何數`n`做 `n & 1` 時,只會有$2^0$項的區別,
而且我們知道奇數最後一個位元一定是1、偶數最後一個位元一定是0,
因此我們可以用`n & 1`來判斷一個數的奇偶性。
* 2的i次方:
我們知道了左移運算子其實就是將數值乘以2,所以只要簡單的用`1 << i`便可以達到2的i次方的目的。
* `2147483647(?`:
要做出 `2147483647` 只要使用 `~(1 << 31)` 就可以了,也不會很難理解。
* 判斷兩數相等:
`a ^ b == 0`
* 兩數互換:
兩數互換有一種最簡單的方式,是使用:
```cpp
int temp = x;
x = y;
y = temp;
```
但是還有一種不需要新變數的單行寫法 ([XOR Swap Algorithm](https://en.wikipedia.org/wiki/XOR_swap_algorithm)) :
```cpp
x ^= y ^= x ^= y;
```
這樣可能不太好理解,我們可以把它拆開來看:
```cpp=
x ^= y; // x = x ^ y
y ^= x; // y = y ^ x
x ^= y; // x = x ^ y
```
> **引理:**
> XOR 位元運算具有下列性質
> 1. 交換律: $A\oplus B=B\oplus A$
> 2. 結合律: $(A\oplus B)\oplus C=A\oplus (B\oplus C)$
> 3. 單位元素: $A\oplus 0=A$
> 4. 元素是自己的反元素: $A\oplus A=0$
1. $X_1 = X_0\oplus Y_0$
2. $Y_1 = Y_0 \oplus X_1 = Y_0 \oplus X_0 \oplus Y_0 = X_0 \oplus (Y_0 \oplus Y_0) = X_0 \oplus 0 = X_0$
3. $X_2 = X_1\oplus Y_1 = (X_0\oplus Y_0)\oplus X_0 = (X_0\oplus X_0)\oplus Y_0 = 0\oplus Y_0 = Y_0$