24. +、-、*、/、= 的優先級
1. 優先級
和數學一樣,C 語言規定先乘除後加減。也就是說,乘法運算符和除法運算符的優先級(Precedence)比加法運算符和減法運算符高。同時,C 語言也規定,如果兩個運算符的優先級相同,並且它們之間沒有被優先級比它們高或者低的運算符隔開,則它們的運算順序根據它們在語句中出現的先後而定。大多數運算符都是從左向右進行運算的,不過也有從右向左進行運算的(例如賦值運算符)。乘法運算符和除法運算符的優先級相同,加法運算符和減法運算符的優先級相同。因此,以下語句
var = 8.0 + 20.0 / 4.0 * 2.0;
運算順序為:
20.0 / 4.0
5.0 * 2.0 (20.0 / 4.0 得 5.0)
8.0 + 10.0
var = 18.0
在這個表達式中,/ 和 * 優先級相同,而且是從左向右進行運算的,所以先運算 20.0 / 4.0,然後才輪到 5.0 * 2.0。
如果我們想讓加法先進行,可以給 8.0 + 20.0 加上括號:
var = (8.0 + 20.0) / 4.0 * 2.0;
這個語句的運算順序為:
8.0 + 20.0
28.0 / 4.0
7.0 * 2.0
var = 14.0
C 語言規定,先進行括號裡面的運算,後進行括號外面的運算。在括號裡面,運算順序和上面討論的一樣。例如:
var = (8.0 + 20.0 / 4.0 * 2.0) / 3.0;
運算順序為:
20.0 / 4.0
5.0 * 2.0
8.0 + 10.0
18.0 / 3.0
var = 6.0
下表總結了這幾個運算符的優先級以及它們的結合律,按優先級從高到低進行排列
運算符 結合律
() 從左向右
+ -(單目) 從右向左
* / 從左向右
+ -(二目) 從左向右
= 從右向左
2. 優先級和運算順序
運算符優先級(Operator precedence)是決定運算順序的重要規則,但不能完全(也沒必要完全)確定運算順序。例如:
5 * 3 + 8 * 4;
根據運算符優先級,我們知道,乘法運算先於加法運算。但是 5 * 3 和 8 * 4 誰先誰後,我們並不能確定。它們運算的先後是由編譯器決定的。這是因為某種運算順序在某種系統中效率更高,而另一種運算順序在另一種系統中效率更高。無論它們的運算先後如何,最終得到的結果都是 47。
您可能會說:“乘法不是從左向右進行運算的嗎?這不是說明最左邊的乘法最先進行嗎?”是的,乘法的確是從左向右進行運算,但是您也要看到,這兩個乘法運算符之間被加法運算符隔開了!我們舉一個例子來說明乘法從左向右進行運算的意思。以下語句
5 * 2 * 9 * 4;
運算順序為:
5 * 2
10 * 9
90 * 4
下面我們來看一個小程序。
/* precedence.c -- 優先級測試 */
#include <stdio.h>
int main(void)
{
int var1, var2;
var1 = var2 = -(9 + 4) * 5 + (6 + 8 * (7 - 1));
printf("var1 = var2 = %d\n", var1);
return 0;
}
請認真閱讀以上程序,想想會出現什麼結果,然後編譯運行,看看運行結果和您想象的是否一樣。
首先,括號運算符優先級最高。但是 (9 + 4) 和 (6 + 8 * (7 - 1)) 運算的先後是由編譯器決定的。假設 (9 + 4) 先進行,則運算後得 13,然後負號運算符作用於 13 得 -13。於是我們得到:
var1 = var2 = -13 * 5 + (6 + 8 * (7 - 1));
在 (6 + 8 * (7 - 1)) 中,先運算 (7 - 1),得:
var1 = var2 = -13 * 5 + (6 + 8 * 6);
因為 * 優先級高於 +,於是我們得到:
var1 = var2 = -13 * 5 + (6 + 48);
進而
var1 = var2 = -13 * 5 + 54;
var1 = var2 = -65 + 54;
var1 = var2 = -11;
因為賦值運算是從右向左的,所以 -11 被賦值給 var2,接著 var2 被賦值給 var1。最終的結果是,var1 和 var2 相等,它們的值都是 -11。
25. 模除運算符 %
% 是模除運算符(Modulus Operator),用於求余數。% 只可用於對整數進行模除,不可用於浮點數。例如:
15 % 2 // 正確。余數為 1
15.2 % 3 // 錯誤!
C99 以前,並沒有規定如果操作數中有負數,模除的結果會是什麼。C99 規定,如果 % 左邊的操作數是正數,模除的結果也是正數;如果 % 左邊的操作數是負數,模除的結果就是負數。例如:
15 % 2 // 余 1
15 % -2 // 余 1
-15 % 2 // 余 -1
-15 % -2 // 余 -1
標准規定,如果 a 和 b 都是整數,則 a % b 可以用公式 a - (a / b) * b 算出。例如:
-15 % 2 == -15 - (-15 / 2) * 2 == -15 - (-7) * 2 == -1
最後,我們看一個小程序。
/* months_to_year.c -- 將用戶輸入的月數轉換成年數和月數 */
#include <stdio.h>
int main(void)
{
int months, years, months_left, months_per_year = 12;
printf("Enter the number of months: ");
scanf("%d", &months);
years = months / months_per_year; /* 算出年數 */
months_left = months % months_per_year; /* 算出剩余的月數 */
printf("%d months is %d years, %d months.\n", months, years, months_left);
return 0;
}
26. 自增運算符和自減運算符
1. 自增運算符(Increment Operator)
自增運算符 ++ 使操作數的值增 1。++ 可以置於操作數前面,也可以放在後面。例如:
++n ;
n++ ;
這兩個語句產生的結果都是使 n 增 1,可以說沒什麼區別。使用以下語句得到的效果也是一樣的:
n = n + 1 ;
盡管上面兩個語句中,++ 前置和後置沒有區別。但是,++ 前置和後置其實是有區別的。例如:
int n = 1, post, pre;
post = n++;
pre = ++n;
對於 post = n++; 這個語句,n 的值被賦予 post 後,n 才增 1。也就是說,這個語句執行完後,post 的值是 1,而 n 的值變成 2。而 pre = ++n; 這個語句,n 先增 1,然後再把自增後的值賦予 pre。也就是說,這個語句執行完後,pre 的值是 3,n 的值也是 3。
由此可得,如果 ++ 前置,則 ++ 的操作數先增 1,然後再參與其它運算;如果 ++ 後置,則 ++ 的操作數先參與其它運算,然後才增 1。嚴格地說,前置 ++ 的操作數的值在被使用之前增 1,而後置 ++ 的操作數的值在被使用之後增 1。例如:
int n = 5, post = 1, pre = 1;
pre = ++n + pre; // 運算結束後 pre 為 7
n = 5;
post = n++ + post; // 運算結束後 post 為 6
2. 自減運算符(Decrement Operator)
自減運算符 -- 使操作數的值減 1。-- 可以置於操作數前面,也可以放在後面。例如:
--n ;
n-- ;
自減運算符和自增運算符非常相似,區別只在於自減運算符使操作數減 1,而自增運算符使操作數增 1。例如:
int n = 5, post = 1, pre = 1;
pre = --n + pre; // 運算結束後 pre 為 5
n = 5;
post = n-- + post; // 運算結束後 post 為 6
3. 優先級
自增運算符和自減運算符的優先級很高,只有圓括號的優先級比它們高。因此,n*m++; 表示 n*(m++); 而不是 (n * m)++; 。而且 (n * m)++; 是錯誤的。因為 ++ 和 -- 的操作數只能是可變左值(modifiable lvalue),而 n * m 不是。
注意,不要把優先級和取值順序混淆了。例如:
int x = 1, y = 2, z;
z = (x + y++) * 3; // 運算結束後 z 為 9,y 為 3
用數字代替上面的語句得:
z = (1 + 2) * 3;
僅當 y 的值被使用後,y 才會增 1。優先級表明的是 ++ 僅作用於 y,而不是 (x + y)。優先級也表明 y 的值何時被使用,但是 y 的值何時增 1 是由自增運算符的本質決定的。
當 y++ 是某個算術表達式的一部分時,您可以認為它表示“先使用 y 的值,然後自增”。類似地,++y 表示“先自增,然後使用自增後的值”。
==========================================================================
以下內容引自《C 語言常見問題集》 原著:Steve Summit 翻譯:朱群英, 孫 雲
http://c-faq-chn.sourceforge.net/ccfaq/index.html
http://www.eskimo.com/~scs/C-faq/top.html
==========================================================================
4.3 對於代碼 int i = 3; i = i++; 不同編譯器給出不同的結果, 有的為 3, 有的為 4, 哪個是正確的?
沒有正確答案;這個表達式無定義。參見問題 3.1, 3.7 和 11.32。 同時注意, i++ 和 ++i 都不同於 i+1。如果你要使 i 自增 1, 使用 i=i+1, i+=1, i++ 或 ++i, 而不是任何組合, 參見問題 3.10。
12.35 有人說 i = i++ 的行為是未定義的, 但是我剛在一個兼容 ANSI 的編譯器上測試, 得到了我希望的結果。
面對未定義行為的時候, 包括范圍內的實現定義行為和未確定行為, 編譯器可以做任何實現, 其中也包括你所有期望的結果。但是依靠這個實現卻不明智。參加問題 7.4, 11.31, 11.32 和 11.34。
4.2 使用我的編譯器,下面的代碼 int i=7; printf("%d\n", i++ * i++); 返回 49?不管按什麼順序計算, 難道不該打印出56嗎?
盡管後綴自加和後綴自減操作符 ++ 和 --
在輸出其舊值之後才會執行運算, 但這裡的``之後"常常被誤解。沒有任何保證確保自增或自減會在輸出變量原值之後和對表達式的其它部分進行計算之前立即進行。也不能保證變量的更新會在表達式 ``完成" (按照 ANSI C 的術語, 在下一個 ``序列點" 之前, 參見問題 3.7) 之前的某個時刻進行。本例中, 編譯器選擇使用變量的舊值相乘以後再對二者進行自增運算。
包含多個不確定的副作用的代碼的行為總是被認為未定義。(簡單而言, ``多個不確定副作用" 是指在同一個表達式中使用導致同一對象修改兩次或修改以後又被引用的自增, 自減和賦值操作符的任何組合。這是一個粗略的定義; 嚴格的定義參見問題 3.7, ``未定義" 的含義參見問題 11.32。) 甚至都不要試圖探究這些東西在你的編譯器中是如何實現的 (這與許多 C 教科書上的弱智練習正好相反); 正如 K&R 明智地指出, ``如果你不知道它們在不同的機器上如何實現, 這樣的無知可能恰恰會有助於保護你。
4.7 我怎樣才能理解復雜表達式?``序列點" 是什麼?
序列點是一個時間點(在整個表達式全部計算完畢之後或在 ||、 &&、 ? : 或逗號 運算符處, 或在函數調用之前), 此刻塵埃落定, 所有的副作用都已確保結束。 ANSI/ISO C 標准這樣描述:
在上一個和下一個序列點之間, 一個對象所保存的值至多只能被表達式的計算修改一次。而且前一個值只能用於決定將要保存的值。
第二句話比較費解。它說在一個表達式中如果某個對象需要寫入, 則在同一表達式中對該對象的訪問應該只局限於直接用於計算將要寫入的值。這條規則有效地限制了只有能確保在修改之前才訪問變量的表達式為合法。例如 i = i+1 合法, 而 a[i] = i++ 則非法 (參見問題 3.1)。
參見下邊的問題 3.8。