算術運算子
算術運算子對其操作數執行標準的數學運算。
| 本節尚不完整 原因:為此表和涵蓋多個主題的其他表考慮一個更通用的目錄 |
| 運算子 | 運算子名稱 | 範例 | 結果 |
|---|---|---|---|
| + | 一元加 | +a | a 在提升(promotion)後的值 |
| - | 一元減 | -a | a 的負值 |
| + | 加法 | a + b | a 與 b 的和 |
| - | 減法 | a - b | 從 a 中減去 b |
| * | 乘積 | a * b | a 與 b 的乘積 |
| / | 除法 | a / b | a 除以 b |
| % | remainder | a % b | a 除以 b 的餘數 |
| ~ | 按位元非 (NOT) | ~a | a 的按位元非結果 |
| & | 按位元與 (AND) | a & b | a 與 b 的按位元與結果 |
| | | 按位元或 (OR) | a | b | a 與 b 的按位元或結果 |
| ^ | 按位元互斥或 (XOR) | a ^ b | a 與 b 的按位元互斥或結果 |
| << | 按位元左移 | a << b | a 左移 b 位元 |
| >> | 按位元右移 | a >> b | a 右移 b 位元 |
目錄 |
[編輯] 溢位
無號整數算術始終以 模 2n
執行,其中 n 是該特定整數中的位元數。例如:對於 unsigned int,將 UINT_MAX 加一會得到 0,而從 0 減一會得到 UINT_MAX。
當有號整數算術運算溢位時(結果超出其類型能表達的範圍),其行為是未定義的:它可能會根據表示法的規則(通常是 2 的補數)發生折返,可能會在某些平台或編譯器選項下觸發陷阱(trap)(例如 GCC 和 Clang 中的 -ftrapv),或者可能會完全被編譯器優化掉。
[編輯] 浮點環境
如果 #pragma STDC FENV_ACCESS 被設置為 ON,則所有浮點算術運算子都會遵循當前的浮點捨入方向,並按照 math_errhandling 指定的方式報告浮點算術錯誤,除非是靜態初始化器的一部分(在此情況下不會引發浮點異常,且捨入模式為向最近偶數捨入)。
[編輯] 浮點收縮
除非 #pragma STDC FP_CONTRACT 被設置為 OFF,否則所有浮點算術都可能如同中間結果具有無限範圍和精度般執行,也就是說優化可能會省略捨入錯誤和浮點異常,而這些若按照書寫方式精確評估則會被觀察到。例如:允許使用單個融合乘加(FMA)CPU 指令實現 (x*y) + z,或將 a = x*x*x*x; 優化為 tmp = x*x; a = tmp*tmp。
與收縮無關,浮點算術的中間結果可能具有與其類型指示不同的範圍和精度,請參閱 FLT_EVAL_METHOD。
[編輯] 一元算術
一元算術運算子表達式的形式為
+ expression |
(1) | ||||||||
- expression |
(2) | ||||||||
| expression | - | 任何算術類型的表達式 |
一元加和一元減首先對其操作數應用整數提升,然後
- 一元加返回提升後的值
- 一元減返回提升後值的負值(除了 NaN 的負值還是另一個 NaN)
表達式的類型是提升後的類型,且其值類別(value category)是非左值(non-lvalue)。
[編輯] 備註
在典型的(2 的補數)平台上,當一元減應用於 INT_MIN、LONG_MIN 或 LLONG_MIN 時,會因有號整數溢位而觸發未定義行為。
在 C++ 中,一元運算子 + 還可以用於陣列和函式等其他內建類型,但在 C 中則不行。
#include <stdio.h> #include <complex.h> #include <limits.h> int main(void) { char c = 'a'; printf("sizeof char: %zu sizeof int: %zu\n", sizeof c, sizeof +c); printf("-1, where 1 is signed: %d\n", -1); // Defined behavior since arithmetic is performed for unsigned integer. // Hence, the calculation is (-1) modulo (2 raised to n) = UINT_MAX, where n is // the number of bits of unsigned int. If unsigned int is 32-bit long, then this // gives (-1) modulo (2 raised to 32) = 4294967295 printf("-1, where 1 is unsigned: %u\n", -1u); // Undefined behavior because the mathematical value of -INT_MIN = INT_MAX + 1 // (i.e. 1 more than the maximum possible value for signed int) // // printf("%d\n", -INT_MIN); // Undefined behavior because the mathematical value of -LONG_MIN = LONG_MAX + 1 // (i.e. 1 more than the maximum possible value for signed long) // // printf("%ld\n", -LONG_MIN); // Undefined behavior because the mathematical value of -LLONG_MIN = LLONG_MAX + 1 // (i.e. 1 more than the maximum possible value for signed long long) // // printf("%lld\n", -LLONG_MIN); double complex z = 1 + 2*I; printf("-(1+2i) = %.1f%+.1f\n", creal(-z), cimag(-z)); }
可能輸出
sizeof char: 1 sizeof int: 4 -1, where 1 is signed: -1 -1, where 1 is unsigned: 4294967295 -(1+2i) = -1.0-2.0
[編輯] 加法運算子
二元算術加法運算子表達式的形式為
lhs + rhs |
(1) | ||||||||
lhs - rhs |
(2) | ||||||||
[編輯] 算術加法與減法
如果兩個操作數皆為算術類型,則
- 首先執行一般算術轉換
- 接著根據一般數學規則對轉換後的操作數進行加減(對於減法,從 lhs 中減去 rhs),除了
- 如果一個操作數是 NaN,則結果是 NaN
- 無限減無限是 NaN,且引發 FE_INVALID
- 無限加負無限是 NaN,且引發 FE_INVALID
複數和虛數的加減法定義如下(請注意,如果兩個操作數都是虛數,則結果類型是虛數;如果一個操作數是實數而另一個是虛數,則結果是複數,如一般算術轉換所指定)
| + 或 - | u | iv | u + iv |
|---|---|---|---|
| x | x ± u | x ± iv | (x ± u) ± iv |
| iy | ±u + iy | i(y ± v) | ±u + i(y ± v) |
| x + iy | (x ± u) + iy | x + i(y ± v) | (x ± u) + i(y ± v) |
// work in progress // note: take part of the c/language/conversion example
[編輯] 指標算術
- 如果指標
P指向索引為I的陣列元素,則
- P+N 和 N+P 是指向索引為
I+N的同一陣列元素的指標 - P-N 是指向索引為
I-N的同一陣列元素的指標
- P+N 和 N+P 是指向索引為
僅當原始指標和結果指標都指向同一陣列的元素或該陣列結尾之後的位置(one past the end)時,行為才是有定義的。請注意,當 p 指向陣列的第一個元素時執行 p-1 是未定義行為,且在某些平台上可能會失敗。
- 如果指標
P1指向索引為I的陣列元素(或結尾後一格),且P2指向同一個陣列中索引為J的元素(或結尾後一格),則
- P1-P2 的值等於 I-J,類型為 ptrdiff_t(這是一種有號整數類型,通常是能聲明的最大物件大小的一半)
僅當結果適合放入 ptrdiff_t 時,其行為才有定義。
為了進行指標算術,指向非陣列元素的物件的指標被視為指向大小為 1 的陣列的第一個元素的指標。
// work in progress int n = 4, m = 3; int a[n][m]; // VLA of 4 VLAs of 3 ints each int (*p)[m] = a; // p == &a[0] p = p + 1; // p == &a[1] (pointer arithmetic works with VLAs just the same) (*p)[2] = 99; // changes a[1][2]
[編輯] 乘法運算子
二元乘法算術運算子表達式的形式為
lhs * rhs |
(1) | ||||||||
lhs / rhs |
(2) | ||||||||
lhs % rhs |
(3) | ||||||||
- 首先執行一般算術轉換。然後...
[編輯] 乘法
二元運算子 * 根據通常的算術定義對其操作數進行乘法運算(在經過一般算術轉換後),除了
- 如果一個操作數是 NaN,則結果是 NaN
- 無限乘以零會得到 NaN,並引發 FE_INVALID
- 無限乘以非零數會得到無限(即使對於複數參數也是如此)
因為在 C 中,任何具有至少一個無限部分的複數值都是無限的,即使其另一部分是 NaN,因此一般的算術規則不適用於複數與複數的乘法。浮點操作數的其他組合遵循下表:
| * | u | iv | u + iv |
|---|---|---|---|
| x | xu | i(xv) | (xu) + i(xv) |
| iy | i(yu) | −yv | (−yv) + i(yu) |
| x + iy | (xu) + i(yu) | (−yv) + i(xv) | 特殊規則 |
除了無限處理外,複數乘法不允許在中間結果發生溢位,除非 #pragma STDC CX_LIMITED_RANGE 被設置為 ON,在此情況下,其值可以按 (x+iy)×(u+iv) = (xu-yv)+i(yu+xv) 進行計算,因為程式設計師承擔了限制操作數範圍和處理無限的責任。
儘管不允許不當的溢位,複數乘法仍可能引發不實的浮點異常(否則實作不溢位版本將非常困難)
#include <stdio.h> #include <stdio.h> #include <complex.h> #include <math.h> int main(void) { // TODO simpler cases, take some from C++ double complex z = (1 + 0*I) * (INFINITY + I*INFINITY); // textbook formula would give // (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN // but C gives a complex infinity printf("%f + i*%f\n", creal(z), cimag(z)); // textbook formula would give // cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN // but C gives ±∞+i*nan double complex y = cexp(INFINITY + I*NAN); printf("%f + i*%f\n", creal(y), cimag(y)); }
可能輸出
inf + i*inf inf + i*nan
[編輯] 除法
二元運算子 / 根據通常的算術定義將第一個操作數除以第二個(在經過一般算術轉換後),除了
- 當經過一般算術轉換後的類型為整數類型時,結果是代數商(而非分數),捨入方向由實作定義(C99 之前)向零截斷(自 C99 起)
- 如果一個操作數是 NaN,則結果是 NaN
- 如果第一個操作數是複無限,而第二個操作數是有限的,則
/運算子的結果是複無限 - 如果第一個操作數是有限的,而第二個操作數是複無限,則
/運算子的結果是零。
因為在 C 中,任何具有至少一個無限部分的複數值視為無限,即使其另一部分是 NaN,因此一般的算術規則不適用於複數與複數的除法。浮點操作數的其他組合遵循下表:
| / | u | iv |
|---|---|---|
| x | x/u | i(−x/v) |
| iy | i(y/u) | y/v |
| x + iy | (x/u) + i(y/u) | (y/v) + i(−x/v) |
除了無限處理外,複數除法不允許在中間結果發生溢位,除非 #pragma STDC CX_LIMITED_RANGE 被設置為 ON,在此情況下,其值可以按 (x+iy)/(u+iv) = [(xu+yv)+i(yu-xv)]/(u2
+v2
) 進行計算,因為程式設計師承擔了限制操作數範圍和處理無限的責任。
儘管不允許不當的溢位,複數除法仍可能引發不實的浮點異常(否則實作不溢位版本將非常困難)
如果第二個操作數為零,其行為是未定義的,但若支援 IEEE 浮點算術且正在進行浮點除法,則
- 將非零數字除以 ±0.0 會得到符號正確的無限,並引發 FE_DIVBYZERO
- 0.0 除以 0.0 會得到 NaN,並引發 FE_INVALID
[編輯] 餘數
二元運算子 % 會產生第一個操作數除以第二個操作數(經過一般算術轉換後)的餘數。
餘數的符號定義為:如果商 a/b 可以在結果類型中表示,則 (a/b)*b + a%b == a。
如果第二個操作數為零,其行為是未定義的。
如果商 a/b 在結果類型中不可表示,則 a/b 和 a%b 的行為都是未定義的(這表示在 2 的補數系統上 INT_MIN%-1 是未定義的)
注意:餘數運算子不適用於浮點類型,函式庫函式 fmod 提供此功能。
[編輯] 按位元邏輯
按位元算術運算子表達式的形式為
~ rhs |
(1) | ||||||||
lhs & rhs |
(2) | ||||||||
lhs | rhs |
(3) | ||||||||
lhs ^ rhs |
(4) | ||||||||
其中
| lhs, rhs | - | 整數類型的表達式 |
首先,運算子 &、^ 和 | 對兩個操作數執行一般算術轉換,而運算子 ~ 對其唯一的操作數執行整數提升。
接著,按位元應用相應的二元邏輯運算子;也就是說,根據應用於操作數對應位元的邏輯運算(NOT、AND、OR 或 XOR),來設置或清除結果的每個位元。
注意:按位元運算子常用於操作位元集(bit sets)和位元遮罩(bit masks)。
注意:對於無號類型(提升後),表達式 ~E 等於結果類型能表達的最大值減去 E 的原始值。
可能輸出
Promoted mask: 0x000000f0 Value: 0x12345678 Setting bits: 0x123456f8 Clearing bits: 0x12345608 Selecting bits: 0x00000070
[編輯] 位移運算子
按位元位移運算子表達式的形式為
lhs << rhs |
(1) | ||||||||
lhs >> rhs |
(2) | ||||||||
其中
| lhs, rhs | - | 整數類型的表達式 |
首先,分別對每個操作數執行整數提升(注意:這與其他皆執行一般算術轉換的二元算術運算子不同)。結果的類型是提升後的 lhs 類型。
如果 rhs 為負,或大於等於提升後的 lhs 中的位元數,則行為是未定義的。
對於無號 lhs,LHS << RHS 的值是 LHS * 2RHS
對(返回類型最大值加 1)取模後的結果(也就是說,執行按位元左移,移出目的類型範圍的位元被捨棄)。對於非負值的有號 lhs,如果 LHS * 2RHS
能在提升後的 lhs 類型中表示,則 LHS << RHS 的值即為該值,否則行為是未定義的。
對於無號 lhs 和非負值的有號 lhs,LHS >> RHS 的值是 LHS / 2RHS
的整數部分。對於負值的 LHS,LHS >> RHS 的值由實作定義,在大多數實作中,這會執行算術右移(以便結果保持為負)。因此在大多數實作中,對有號 LHS 進行右移會用原始符號位元填充新的高位位元(即如果原為非負則填入 0,若為負則填入 1)。
#include <stdio.h> enum {ONE=1, TWO=2}; int main(void) { char c = 0x10; unsigned long long ulong_num = 0x123; printf("0x123 << 1 = %#llx\n" "0x123 << 63 = %#llx\n" // overflow truncates high bits for unsigned numbers "0x10 << 10 = %#x\n", // char is promoted to int ulong_num << 1, ulong_num << 63, c << 10); long long long_num = -1000; printf("-1000 >> 1 = %lld\n", long_num >> ONE); // implementation defined }
可能輸出
0x123 << 1 = 0x246 0x123 << 63 = 0x8000000000000000 0x10 << 10 = 0x4000 -1000 >> 1 = -500
[編輯] 參考資料
- C17 標準 (ISO/IEC 9899:2018)
- 6.5.3.3 一元算術運算子 (p: 64)
- 6.5.5 乘法運算子 (p: 66)
- 6.5.6 加法運算子 (p: 66-68)
- 6.5.7 按位元位移運算子 (p: 68)
- 6.5.10 按位元與運算子 (p: 70)
- 6.5.11 按位元互斥或運算子 (p: 70)
- 6.5.12 按位元或運算子 (p: 70-71)
- C11 標準 (ISO/IEC 9899:2011)
- 6.5.3.3 一元算術運算子 (p: 89)
- 6.5.5 乘法運算子 (p: 92)
- 6.5.6 加法運算子 (p: 92-94)
- 6.5.7 按位元位移運算子 (p: 94-95)
- 6.5.10 按位元與運算子 (p: 97)
- 6.5.11 按位元互斥或運算子 (p: 98)
- 6.5.12 按位元或運算子 (p: 98)
- C99 標準 (ISO/IEC 9899:1999)
- 6.5.3.3 一元算術運算子 (p: 79)
- 6.5.5 乘法運算子 (p: 82)
- 6.5.6 加法運算子 (p: 82-84)
- 6.5.7 按位元位移運算子 (p: 84-85)
- 6.5.10 按位元與運算子 (p: 87)
- 6.5.11 按位元互斥或運算子 (p: 88)
- 6.5.12 按位元或運算子 (p: 88)
- C89/C90 標準 (ISO/IEC 9899:1990)
- 3.3.3.3 一元算術運算子
- 3.3.5 乘法運算子
- 3.3.6 加法運算子
- 3.3.7 按位元位移運算子
- 3.3.10 按位元與運算子
- 3.3.11 按位元互斥或運算子
- 3.3.12 按位元或運算子
[編輯] 參閱
| 常用運算子 | ||||||
|---|---|---|---|---|---|---|
| 賦值 | 遞增 遞減 |
算術 | 邏輯 | 比較 | 成員 存取 |
其他 |
|
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
| 算術運算子 的 C++ 檔案
|