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