國內許多C語言教科書,在介紹C語言運算符時,都把所謂的“單目運算符”歸納為優先級相同結合性從右向左的運算符。例如號稱是“我國廣大初學者學習C語言程序設計的主流用書”的《C程序設計》(譚浩強著,清華大學出版社,2010年6月(第四版))的378頁附錄D 運算符和結合性 中就是這樣:
附錄D 運算符和結合性
優先級 運算 含義 要求運算對象的個數 結合方向
1
( ) 圓括號 自左至右
[ ] 下標運算符
-> 指向結構體成員運算符
. 結構體成員運算符
2 1
(單目運算符) 自右至左
! 邏輯非運算符
~ 按位取反運算符
++ 自增運算符
-- 自減運算符
- 負號運算符
(類型) 類型轉換運算符
* 指針運算符
& 取地址運算符
sizeof 長度運算符
殊不知,這種歸納是完全錯誤的。而且恰恰由於《C程序設計》是所謂的“主流用書”,其錯誤帶來的影響也是廣泛普遍的和災難性的。(google或百度一下“所有的單目運算符具有相同的優先級”,你就會知道我是不是在誇大其詞危言聳聽)。
為了揭示“所有的單目運算符具有相同的優先級”的錯誤,下面首先按照這種錯誤的說法進行一個實驗。
我們都知道,對於
int i;
來說,&i是求得一個指向i的指針(注意這裡的“&”是一個“單目運算符”),&i的數據類型顯然是“int *”。
如果對“int *”類型的表達式“&i”做“(int *)”類型轉換運算(可能顯得有點無聊)
(int *)&i
得到顯然還是“&i”——值和類型都沒有任何改變。
按照“所有的單目運算符具有相同的優先級”這個錯誤的說法,由於“&” 和“(int *)”的結合性從右向左
(int *)&i
這個表達式沒有任何毛病,也不需要通過加“()”來明確運算對象。
現在,再對
(int *)&i
這個表達式做sizeof運算,由於sizeof也和(int *)同級(注意這是錯誤的),結合性從右向左,所以可以直接把sizeof寫在(int *)&i 的左面,即
sizeof (int *) & i
顯然,這個表達式的運算結果和sizeof (int *)應該一模一樣,因為(int *) & i的數據類型是(int *) 。
然而,如果你在機器上跑一下下面的代碼的話
view sourceprint?#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int i ;
printf(" %u " , sizeof (int *) ) ;
printf(" %u " , sizeof (int *) & i ) ;
system("PAUSE");
return 0;
}
做為目睹了悖論產生過程的觀眾,我想你非常清楚,共同參與制造這個荒唐悖論的只有編譯器、我和“主流用書”中的所謂“所有的單目運算符具有相同的優先級”的理論。所以如果你同意C語言本身應該是一個嚴密的邏輯體系的話,你應該能想到產生這個悖論的原因只能是三者之中至少有一個犯了錯誤。
編譯器BUG?你覺得可能嗎?如此簡單的表達式都算錯,編譯器廠家還怎麼混?
我的推理過程有錯?可能嗎?這比前一條更加沒可能。
所以,錯誤只可能出現在“所有的單目運算符具有相同的優先級”這句話上。
這句話的究竟有什麼錯誤呢?錯誤就在於,“(類型)”這種運算符的優先級其實低於sizeof 和 一元 & 運算符。由於類型轉換運算符的優先級低於sizeof,所以
sizeof (int *) & i
不可能表示sizeof ((int *) & i)這樣的含義,因為C語言的優先級和結合性規定只容許運算符作用於高級表達式或同級表達式。
且住,“高級表達式”和“同級表達式”是啥你可能看不懂,因為這是我為了敘述方便發明的新術語。不過咱不是那種發明術語而不做任何解釋管殺不管埋之學風惡劣之人。我解釋一下,高級表達式是指相對某運算符來說,只出現更高優先級運算符的表達式或基本表達式。例如,對於(二元)+運算符來說,3*5就是高級表達式。同級表達式是指相對某運算符來說,不出現更低級運算符的表達式。例如對於(二元)+運算符,2+3就是它的同級表達式,但a=b就不是,因為這裡出現了=,=的優先級比+要低。
舉例來說,由於(二元)+的優先級低於(二元)*,那麼可以對
3*6 進行 + 運算:3*6+2
再如由於+和+優先級相同,所以可以對3+6做+運算
3+6+2
但不可以對3+6做*運算
3+6*2
雖然合法,但絕對不可能是(3+6)*2的含義。
當把一個運算符添加在高級表達式或同級表達式上是還必須遵守結合性的規定,由於(二元)+運算的結合性是從左到右,所以只能加到高級表達式或同級表達式的右邊。當然還得給它加的另一個操作數,這個操作數必須是高級表達式。
由於 (int *) & i 不是 sizeof 的高級表達式或同級表達式,所以希望對它做sizeof運算必須加括號,寫成sizeof ((int *) & i)。(注:((int *) & i)構成了一個基本表達式)
而寫成 sizeof (int *) & i 的話,就如同前面在3+6加上*一樣不是(3+6)*2的含義而是3+(6*2)的含義一樣,表達的可能是另一種含義,這個含義是
( sizeof (int *) ) & i
這裡&其實是二元&運算。
既然是&是二元&運算,前面代碼中沒有給 i 初值 顯然不妥,正確的代碼是:
view sourceprint?#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int i = 3 ; //whatever
printf(" %u " , sizeof (int *) ) ;
printf(" %u " , sizeof (int *) & i ) ;
return 0;
}