算術運算子是程式最基礎的計算工具。C 語言在整數除法、取模、字元自動提升、自增自減副作用等細節上有獨特規則,稍有不慎即導致邏輯錯誤。
| 運算子 | 名稱 | 目數 | 範例 | 結果 |
|---|---|---|---|---|
+ | 加 / 正號 | 二元/一元 | a+b, +5 | 和 / 值本身 |
- | 減 / 負號 | 二元/一元 | a-b, -5 | 差 / 相反數 |
* | 乘 | 二元 | a*b | 積 |
/ | 除 | 二元 | a/b | 商 (整數截斷) |
% | 取模 | 二元 | a%b | 餘數 |
++ | 自增 | 一元 | ++a, a++ | 前/後增 1 |
-- | 自減 | 一元 | --a, a-- | 前/後減 1 |
15 / 2 → 7 // 整數除法:截斷向零 (C99 標準)
15 % 2 → 1 // 餘數滿足:(a/b)*b + a%b == a
-15 / 2 → -7 // C99: 向零截斷
-15 % 2 → -1 // 餘數符號同被除數 (C99)
15 % -2 → 1
鐵律:% 不可用於浮點數 (編譯錯誤),浮點數餘數用 fmod (math.h)。
char 在運算式中自動提升為 int (ASCII 值)。
char m = 'c'; // ASCII 99
char n = m + 'A' - 'a'; // 'A'=65, 'a'=97 → 99 + 65 - 97 = 67 → 'C'
printf("%c\n", n); // 輸出 C
應用:大小寫轉換、字元分類、簡易加密。
| 形式 | 名稱 | 運算順序 | 運算式值 | 範例 |
|---|---|---|---|---|
++a | 前綴自增 | 先增後用 | 新值 | b = ++a; // a=6, b=6 |
a++ | 後綴自增 | 先用後增 | 舊值 | b = a++; // a=6, b=5 |
--a | 前綴自減 | 先減後用 | 新值 | |
a-- | 後綴自減 | 先用後減 | 舊值 |
int a = 5;
int b = a++ + ++a; // 未定義行為!a 被修改兩次且無序列點
// 不同編譯器結果不同:可能 11, 12, 13
int c = 5;
c = c++ + c++; // 未定義行為
規則:單一運算式中,同一變數至多修改一次,且讀取值必須用於計算新值 (如 a = a + 1)。
| 優先級 | 運算子 | 結合性 |
|---|---|---|
| 高 | ++ -- (前綴) + - (一元) | 右→左 |
| ↓ | * / % | 左→右 |
| ↓ | + - (二元) | 左→右 |
| 低 | ++ -- (後綴) | 左→右 (但實際綁定最緊) |
實用口訣:一元 > 乘除模 > 加減 > 賦值。後綴 ++/-- 優先級最高但「先用後改」語義特殊。
// 整數提升:char/short → int
// 算術轉換:低精度 → 高精度
// unsigned 會「感染」有號數轉為無號運算
int a = 10;
unsigned int b = 20;
a + b // a 轉為 unsigned,結果 unsigned (30)
int c = -1;
unsigned int d = 1;
c > d // c 轉為 unsigned (極大值) → 真!極度危險
防禦:避免有號/無號混合比較;必要時強制轉型 (int)u 或 (unsigned)i。
| 錯誤 | 代碼 | 問題 | 修正 |
|---|---|---|---|
| 整數除法截斷 | float avg = sum / n; | 先整除再轉浮點 | float avg = (float)sum / n; |
| 取模用於浮點數 | float f = 3.14 % 2; | 編譯錯誤 | fmod(3.14, 2) (math.h) |
| 自增自減副作用 | a[i] = i++; | 未定義行為 (i 修改+讀取) | 拆分語句 |
| 有號/無號比較 | if (-1 < 1u) | -1 轉巨大無號數 → 假 | 統一型別或強制轉型 |
| 字面值溢位 | int x = 2147483648; | 超出 int 範圍 | 2147483648L 或 2147483648u |
算術運算看似簡單,實則暗藏「整數截斷、取模限制、字元提升、自增副作用、有號無號混合、優先級綁定」六大陷阱。熟記「除法先轉浮點、取模只用整數、字元運算靠 ASCII、++/-- 拆行寫、有號無號不混比、括號明確優先級」六字訣,能避開 95% 的算術 Bug。