位運算是 C 語言最貼近硬體的特性之一,廣泛應用於嵌入式暫存器操作、網路協定封包解析、壓縮儲存、加密演算等場合。本文系統整理位域、位運算子、複合指派、typedef 別名定義等核心知識,並提供實戰範例與最佳實務建議。
struct BitField {
unsigned int a : 1; // 1 bit
unsigned int b : 3; // 3 bits
unsigned int c : 4; // 4 bits
}; // 總共 8 bits,通常壓縮在 1 個 unsigned int (16/32 bits) 內
int、unsigned int、signed int(C99 允許 _Bool)。&bf.a 錯誤),不可跨儲存單元(編譯器自動對齊)。struct BitField bf = {1, 7, 15};
struct BitField *p = &bf;
p->a = 0;
p->b &= 3; // 7 & 3 = 3
p->c |= 1; // 15 | 1 = 15
printf("%d,%d,%d\n", p->a, p->b, p->c); // 0,3,15
位域成員可直接當整數運算,編譯器自動生成遮罩、位移指令。
#pragma pack(1)
struct IPv4Header {
unsigned int version : 4;
unsigned int ihl : 4;
unsigned int dscp : 6;
unsigned int ecn : 2;
unsigned int total_len : 16;
// ... 其他欄位
};
#pragma pack()
// 直接讀取 version = hdr->version,無需手動位移遮罩
| 運算子 | 名稱 | 範例 | 結果說明 |
|---|---|---|---|
& | 按位與 | 0b1100 & 0b1010 | 0b1000(兩邊皆 1 才為 1) |
| | 按位或 | 0b1100 | 0b1010 | 0b1110(任一為 1 即為 1) |
^ | 按位異或 | 0b1100 ^ 0b1010 | 0b0110(不同為 1,相同為 0) |
~ | 按位取反 | ~0b1100 | 0b...0011(視型別寬度補齊) |
<< | 左移 | 0b0011 << 2 | 0b1100(低位補 0,等價 ×4) |
>> | 右移 | 0b1100 >> 2 | 0b0011(無符號補 0,有符號算術移位保號) |
x &= y; // x = x & y
x |= y; // x = x | y
x ^= y; // x = x ^ y
x <<= n; // x = x << n
x >>= n; // x = x >> n
// 清除第 n 位
x &= ~(1U << n);
// 設定第 n 位
x |= (1U << n);
// 翻轉第 n 位
x ^= (1U << n);
// 測試第 n 位
bool is_set = (x >> n) & 1U;
// 取最低位 1
x & -x; // 利用二補數特性
// 清除最低位 1
x &= (x - 1); // Brian Kernighan 演算法核心
// 判斷 2 的冪次
(x & (x - 1)) == 0 && x != 0;
// 交換兩數(無暫存變數)
a ^= b; b ^= a; a ^= b;
typedef int INTEGER; // 標量別名
typedef char NAME[20]; // 陣列別名
typedef struct { ... } STU; // 結構體別名
typedef void (*Handler)(int); // 函式指標別名
| 特性 | typedef | #define |
|---|---|---|
| 處理階段 | 編譯期(語義分析) | 預處理期(文字替換) |
| 指標宣告 | typedef int *IntPtr; IntPtr a, b; 皆為 int* | #define IntPtr int* IntPtr a, b; → int* a, b; (b 是 int) |
| 作用域 | 遵循區塊作用域 | 全域文本替換 |
| 除錯資訊 | 保留型別名稱 | 無型別資訊 |
結論:永遠優先使用 typedef 定義型別別名,避免 #define 造成指標宣告陷阱。
printf 僅能印順序值。int 寬度均為實作定義,跨平台需謹慎。int count_bits(unsigned x) 計算 1 的個數(查表法 / Kernighan 法 / 編譯器內建 __builtin_popcount)。typedef char *StrPtr; StrPtr a, b; 使 a、b 皆為 char*,而 #define StrPtr char* 會導致 b 為 char。