復合賦值運算符將二元運算與賦值融合,左操作數**只求值一次**(對有副作用的表達式至關重要)。C 提供 10 種復合賦值:
| 運算符 | 等價展開 | 適用類型 |
|---|---|---|
+= | a = a + (b) | 算術、指針 |
-= | a = a - (b) | 算術、指針 |
*= | a = a * (b) | 算術 |
/= | a = a / (b) | 算術 |
%= | a = a % (b) | 整數 |
&= | a = a & (b) | 整數 |
|= | a = a | (b) | 整數 |
^= | a = a ^ (b) | 整數 |
<<= | a = a << (b) | 整數 |
>>= | a = a >> (b) | 整數 |
int a[10], i = 0;
a[i++] += 5; // i 只增 1 次!
// 等價於
int tmp = a[i] + 5;
i++;
a[i-1] = tmp;
// 而非 a[i++] = a[i++] + 5(未定義行為)
這是復合賦值相對展開形式的**語義保證**,也是編寫安全宏、避免副作用重複的關鍵。
?:。a += b += c 等價於 a += (b += c),先執行右側。// 表達式:a += b %= a -= b
// 假設初始 a=10, b=3
// 求值順序(右結合,從最右運算符開始):
// 1. a -= b → a = 10 - 3 = 7
// 2. b %= a → b = 3 % 7 = 3
// 3. a += b → a = 7 + 3 = 10
// 最終:a=10, b=3
// 驗證代碼
int a = 10, b = 3;
a += b %= a -= b;
printf("a=%d, b=%d\n", a, b); // 輸出 a=10, b=3
int arr[10] = {0};
int *p = arr;
p += 3; // p 指向 arr[3],等價於 p = p + 3
p -= 2; // p 指向 arr[1]
// 步長由 sizeof(int) 決定,非字節偏移
| 陷阱代碼 | 問題 | 修正/說明 |
|---|---|---|
a += b *= c | 優先級混淆,實為 a += (b *= c) | 加括號明確意圖:a += (b *= c) 或拆分語句 |
ptr += sizeof(int) | 指針算術自動按元素大小縮放,手動加 sizeof 會重複縮放 | 直接 ptr += 1 移動一個元素 |
a = b += c | 合法但可讀性差,a 得到賦值後的 b | 拆分:b += c; a = b; |
a += b + c * d | 右側完整表達式參與運算 | 等價 a = a + (b + c * d),符合預期 |
| 浮點精度累積 | float sum = 0; for (...) sum += x; 精度隨次數下降 | 用 double、Kahan 求和、或定期校正 |
count++、sum += x、mask |= BIT 簡潔且語義明確。a += b -= c 難讀,拆分為多語句。#define ADD_TO_TOTAL(x) (total += (x)) 保證 x 只求值一次。flags |= FLAG 置位、flags &= ~FLAG 清位、flags ^= FLAG 翻轉,地道高效。-Wparentheses -Wsequence-point 捕獲優先級/副作用問題。+= 等復合賦值運算符是 C 語言語法糖與語義保證的結合:左操作數單次求值、右結合、優先級最低。熟練掌握其等價展開、指針算術行為、鏈式求值順序,能寫出更簡潔、高效、無副作用 Bug 的代碼。**記住:指針 `+=` 移動元素非字節,宏裡用它防重複求值,鏈式賦值拆開寫**。