
摘要:C語言教學過程中普遍存在一個對C語言中自加運算符的錯誤認識,根據C99規范對該錯誤進行詳細分析,指出后綴加運算符與前綴加運算符的本質區別,解釋C語言中令人頗難理解的一個特性。
關鍵詞:C語言;自加運算;后綴加;前綴加;優先級
文章編號:1672-5913(2013)03-0026-03
中圖分類號:G642
隨著計算機教育的普及,C語言已經成為各所高校理工科專業的必修課程。由于C語言本身是為熟練程序員準備的程序設計語言,使用非常靈活,但同時又極容易出錯,因此對教學者提出了更高的要求。
筆者在10多年的教學過程中,發現自己對C語言的某些知識理解是錯誤的;在與同行交流時,也發現某些同行由于缺乏實際的編程經驗,犯下一些知識性的錯誤。目前使用c語言容易犯的錯誤已高達100多種,其中有一個對自加運算符的理解錯誤最為普遍,流傳甚廣,卻至今無人明確提出,筆者擬對此作出澄清。
1 關于后綴自加的一個主流觀點
自加運算符(++)是C語言中使用最普遍的一個運算符(由于自減運算符存在的問題與自加完全相同,下面只談自加運算符),它分為前綴加和后綴加兩種。其中前綴加的運算規則相對比較簡單,一般均可正確理解。但后綴加的運算規則理解起來要困難得多,筆者發現不少同仁均在此處犯錯,且極不容易發現。
在我國高校中使用最廣泛的C語言教材是譚浩強先生的《C語言程序設計》,至2009年,該書已經再版3次,發行量超過1000萬冊,可謂影響深遠。該書在介紹自加運算符時,先是如此說明:
++i,--i(在使用i之前,先使i的值加或減1)
i++,i--(在使用i之后,使i的值加或減1)
并補充解釋到:“但++i和i++的不同之處在于++i是先執行i=i+1后,再使用i的值;而i++是先使用i的值后,再執行i=i+1”。隨后舉了一個程序例子:
i=3:
j=++i;//j的值為4
i=3;
j=i++;//j的值為3,然后i變為4
在程序之后,作者又補充到:“注意(i++)是先用i的原值進行運算以后,再對i加1”。從上述描述不難看出,譚浩強先生對于“j=i++”執行的過程可以理解為下面兩步操作:
i→j
i+1→i
從中可以看出,后綴加的運算優先級比賦值號的優先級更低。該觀點在隨后的兩個版本中雖然描述文字有所出入,但基本思想一直保留。譚浩強先生的這一觀點受到普遍認同,筆者隨機找到10余種C語言程序設計書籍,均同意這一觀點。雖然各書所舉的例子不盡相同,但都能印證這一解釋的正確性。于是這一解釋成為目前C語言教學中的主流觀點,筆者曾不止一次聽到在課堂教學中老師將這一解釋介紹給學生,并將其簡單總結為:“前綴加是在其他操作之前加1,后綴加是在賦值操作之后加1”。
2 無法解釋的矛盾
上述觀點在僅有算術運算的情況下能夠正確解釋程序運行的結果,再加上譚浩強先生的權威,因此成為了主流觀點。但實際上,這一觀點有一個重大的漏洞。按照該觀點,前綴加和后綴加必須是兩個運算符,其中前綴加的優先級高于賦值運算,而后綴加的優先級則低于賦值運算。而所有的C語言教材都承認,自加運算符的優先級只有一個,位于所有運算符中的第二級。這一規定是由ISO/IEC組織在《ISO/ICE 9899:1999,C programming language standard》(以下簡稱C99標準)中規定,教材作者無權更改。
為解決這一矛盾,明確提出:“把前綴自增(自減)和后綴自增(自減)運算符看成兩種運算符,且規定前綴自增(自減)運算符的優先級大于算術運算符,后綴自增(自減)運算符的優先級低于賦值運算符”,即需要修改C99標準。
暫且不提修改C99標準這一愿望是否能實現,即便修改了C99標準暫時解決這一矛盾,但由于這一觀點的內涵是錯誤的,必將會在其他方面暴露出來,下面看一個程序段:
int a[2]={1,2},t;
int*p=a;
t=*p++;
printf("t=%d,a[0]=%d,&a[0]=%p,&a[1]=%p,p=%p",t,a[0],&a[0],&a[1],p);
按照前述“后綴加運算優先級低于賦值號”的觀點C99標準中自加運算符位于所有運算符中第二級的規定,該程序的第4行應該這么來理解:“*”的優先級最高,它和p結合,于是將p所指向的存儲單元內容(a[0]的值,等于1)賦值給t;然后執行后綴加運算,由于后綴加的優先級低于“*”,因此運算數p應先執行“*”運算后,得到的后果再執行后綴加運算。于是后綴加運算是將p所指向的存儲單元(即a[0])的值加1,而p里面存儲的地址值不發生變化,還是a[0]的地址值。依次預計該程序輸出的結果應為:
t=1,a[0]=2,&a[0]=a[0]的地址值,&a[1]=a[1]的地址值,p=a[0]的地址值
但實際上,任何一個合格的C語言使用者都能憑經驗預測出這段代碼執行的實際結果應該為:t=1,a[0]=1,p的指針值從a[0]處移動到了a[1]處。該程序實際運行結果如圖1所示。
這一結果與前面理論預測不符。為此,譚浩強的書中作了如下解釋:“*p++,由于++和*同優先級,是自右而左的結合方向,因此它等價于*(p++)。作用是先得到p指向的變量的值(即*p),然后再使p+1→p”。這一解釋也受到其他教材作者的贊同。
這一解釋單獨看起來似乎沒有問題,但聯系前面的例子“j=i++”,就會發現一個無法解釋的矛盾:在“t=*p++”中,后綴加的優先級必須與“*”相同,然后根據結合方向(自右向左)它才有資格與變量p結合,這時它的優先級遠高于賦值號“=”。而在“j=i++”中,后綴加的優先級則必須低于“=”,否則結果不對。
從這兩個例子可看出,如果按照通常的解釋,同是后綴加運算符,在不同的表達式中擁有了兩個截然不同的優先級!
更為奇怪的是,即便在同一個表達式“t=*p++”中,為了讓++有資格和p結合,規定后綴加的優先級與“*”相同,高于賦值號。但在實際賦值過程中,卻是先將p現在所指向的存儲單元中的值賦給了t,然后再將p中的地址值加1,這時同一個后綴加的優先級又比賦值號更低。這種忽高忽低的優先級規定,很令人費解。
之所以出現這么奇怪的情況,完全是因為目前教學界的主流觀點對于后綴加的錯誤認識所導致的。
3 正確的解釋
其實,關于自加運算符,C99標準中有明確的規定:“++”運算符只有一個,優先級位于第二級。其中前綴加和后綴加的區別在于:按照優先級規定,前綴加直接作用在自加變量上,然后用自加變量本身的值參與后續運算;而后綴加則需要先將自加變量賦值給一個中間變量,然后自加變量加1,再用中間變量參與后續運算。后綴加相對于前綴加而言,其中的差別在于多了一個中間變量,參與后續運算的是這個中間變量,而非自加變量本身。
遵照這一個規定,很容易解釋“j=i++”的結果,它實際上執行了以下3步:
i→i' //i'為系統自動產生的中間變量
i+1→i
i'→j //將i自加前的值賦給了j
對于更為復雜的語句“t=*p++”,也可以用同樣的方式解釋,它實際上執行了以下3步:
p→p' //p'為系統自動產生的中間變量
p+1→p
*p'→t
即無論在何種情況下,后綴加始終保持了同一運算優先級,邏輯間沒有任何沖突。由此可見,C99標準的規定不僅更權威,也比教學界的主流觀點更合理、更簡潔。
更進一步,根據C99這一規定,我們還可以對C語言中看上去更難理解的一個問題作出合理的解釋。幾乎所有的教材都認可這一結論:前綴加比后綴加的效率更高,比如“++k”比“k++”的效率高,但很少有教材指出具體是什么原因。現在知道:這是因為“k++”比“++k”多了一次為中間值賦值的操作。
4 結語
教學界關于后綴加的錯誤認識流傳近20年,影響數百萬名學生,卻至今還未得到明確的糾正,這一事實令人感到遺憾。
實際上,這一知識理解上的偏差,程序設計者通過大量編程鍛煉與思索是可以發現的,而教材作者通過閱讀C99標準也能發現。這一錯誤廣泛流傳的事實說明,C語言教師和教材作者僅憑閱讀幾本相關教材以及少量的編程經驗是難以真正掌握c語言精髓的。C語言盡管靈活,但仍然是精確的計算機程序設計語言,對任何基礎知識理解的偏差,都有可能導致程序設計錯誤,因此教師有責任為初學者傳授準確的知識。這就要求教師和教材作者必須通過實際編程訓練和閱讀更權威的標準說明,加深和驗證自己對C語言的理解。
(見習編輯:劉麗麗;編輯:趙廓)