周二強 張妍琰
(河南城建學院計算機科學與工程學院 河南平頂山 467036)
國內(nèi)現(xiàn)行C語言教材大多不介紹序列點,這使得讀者只能記憶而不是遵循求值規(guī)則分析有序列點表達式的求值順序。以簡單的逗號表達式a=3,++a為例,設變量a為整型變量(下面表達式中出現(xiàn)的變量也默認為整型)?!岸禾柋磉_式自左向右依次求值”,故先對子表達式a=3求值,再對子表達式++a求值。在實際教學中善于質(zhì)疑的學生往往會問:表達式中自增操作符++的優(yōu)先級最高,為何要先求子表達式a=3的值?表達式求值時不是先算高優(yōu)先級的操作符嗎?表達式究竟有沒有求值原則呢?序列點作用的缺失不僅使得學生對C語言知識的認知殘缺不全,而且也影響了學生自主學習的積極性,不利于創(chuàng)新型人才的培養(yǎng)。
根據(jù)C語言標準[1][2][3],序列點就是執(zhí)行序列中的一些特定點,在這些點上,前面求值的副效應(sideeffect)應徹底完成且其后求值的副效應均未發(fā)生。在教材中照搬標準讓初學者學習理解序列點這個概念是不明智的,應直接向初學者指出序列點在表達式求值中起的作用。如表達式中有序列點,則求值時序列點左邊的操作數(shù)要先于其右邊的操作數(shù)求值。C語言表達式求值的原則為:先考慮序列點,再根據(jù)操作符的優(yōu)先級和結(jié)合性。
雖然表達式求值時要首先根據(jù)序列點確定求值順序,但在確定有序列點操作符的操作數(shù)時需要結(jié)合操作符的優(yōu)先級和結(jié)合性。操作數(shù)是指操作符進行操作的操作對象,如表達式3+2中加法操作符左邊的操作數(shù)為3,右邊的操作數(shù)為2。在復雜的表達式中,需結(jié)合操作符的優(yōu)先級和結(jié)合性來確定某操作符的操作數(shù)。對于表達式3+2*5,加法操作符左邊的操作數(shù)為3,但其右邊的操作數(shù)不為2,進行加法運算時3顯然不可能和2相加。因為加法操作符右邊相鄰的操作符為乘法操作符,優(yōu)先級比它高,所以它右邊的操作數(shù)為2*5(的積)。而乘法操作符左邊的操作數(shù)為2,右邊的操作數(shù)為5。對于表達式3+2-5,加法操作符右邊相鄰的操作符為減法操作符,兩者優(yōu)先級相同,但結(jié)合性為左結(jié)合,故它右邊的操作數(shù)為2。
表達式a=0&&++a中邏輯與左邊的操作數(shù)為0,右邊的操作數(shù)為子表達式++a,整個表達式為賦值表達式;而表達式(a=0)&&++a中邏輯與左邊的操作數(shù)為子表達式(a=0),右邊的操作數(shù)為子表達式++a,整個表達式為邏輯表達式。邏輯與操作符有序列點,因此表達式(a=0)&&++a求值時,雖然自增操作符的優(yōu)先級最高,但求值時首先考慮序列點,邏輯與操作符左邊的操作數(shù)(a=0)需先于其右邊的操作數(shù)++a求值。
逗號操作符(,)有序列點。逗號操作符多用于把多條語句變成一條語句,如a=2;和++a;為兩條語句,而a=2,++a;是一條語句。語句a=2,++a;執(zhí)行時,如果逗號操作符沒有序列點,子表達式++a就會先執(zhí)行,即這條語句的執(zhí)行順序與上面兩條語句的并不相同?;诙禾柌僮鞣淖饔?,逗號操作符只能優(yōu)先級最低,且含有序列點。
邏輯與操作符(&&)有序列點。C語言中邏輯與操作符實行“短路計算”,即當其左邊的操作數(shù)值為0即假時,不對右邊的操作數(shù)求值而直接把0(假)作為求值的最終結(jié)果。如果邏輯與操作符沒有序列點,表達式3>5&&++a求值時,自增操作符的優(yōu)先級最高,子表達式++a應先求值。邏輯與操作符左邊的操作數(shù)3>5求值的結(jié)果為0,即假,根據(jù)短路計算,作為邏輯與右邊的操作數(shù),子表達式++a不會被求值。顯然兩者矛盾。為了短路計算,邏輯與操作符&&需要序列點。有了序列點之后,求值時序列點左邊的操作數(shù)將先于其右邊的操作數(shù)求值,即子表達式3>5先求值,由于短路計算,整個表達式的值為0,即假,且右操作數(shù)子表達式++a不會被求值。
邏輯或操作符(||)有序列點。C語言中邏輯或操作符也實行“短路計算”,因此,其有序列點的必要性與邏輯與操作符的相同。
條件操作符?:的問號處?有序列點。條件操作符常用于改寫簡單的if-else選擇結(jié)構(gòu),如下面的語句:
可用條件操作符改寫為a>b?++a:++b;。
如果條件操作符沒有序列點,語句a>b?++a:++b;執(zhí)行時,++a和++b會先于子表達式a>b執(zhí)行,這樣的執(zhí)行順序顯然與if-else選擇結(jié)構(gòu)的不同,因此,條件操作符?:的問號?處有序列點。語句a>b?++a:++b;執(zhí)行時,問號處?左邊的操作數(shù)a>b先執(zhí)行,值為真時,對++a求值,不對++b求值;值為假時,反之。
設整型變量a的值為0。對于表達式'a'||(a=1)&&(a+=2),邏輯或||左邊的操作數(shù)為'a',右邊的操作數(shù)為(a=1)&&(a+=2);邏輯與&&左邊的操作數(shù)為(a=1),右邊的操作數(shù)為(a+=2)。求值時首先考慮序列點,邏輯或左邊的操作數(shù)'a'的值非“0”為真,執(zhí)行短路計算,其右邊的操作數(shù)不會被求值,因此,整個表達式的值為1,即真。在表達式求值的過程中,變量a的值沒有改變。
對于表達式(a=0)&&(a=5)||(a+=1),邏輯與&&左邊的操作數(shù)為(a=0),右邊的操作數(shù)為(a=5);邏輯或||左邊的操作數(shù)為(a=0)&&(a=5),右邊的操作數(shù)為(a+=1)。求值時首先考慮序列點,邏輯與&&左邊的操作數(shù)(a=0)先求值,求值時一方面變量a的值會變成0,另一方面這個子表達式的值為0即假,執(zhí)行短路計算,其右邊的操作數(shù)(a=5)不會被求值,因此,原表達式變?yōu)?||(a+=1)。邏輯或||左邊的操作數(shù)值為0,即假,不能短路計算,只能再求其右邊操作數(shù)的值。子表達式(a+=1)求值時一方面變量a的值由0變成1,另一方面這個子表達式的值為1即真,因此,原表達式的值為1,即真。表達式的求值完成后,變量a的值變?yōu)?。
特別強調(diào),用賦值表達式作為邏輯操作符的操作數(shù)僅僅為了清晰地表明在表達式求值過程中某個子表達式是否被求值,這類表達式的實用性和可讀性非常差,實際編程時不能使用這類表達式。
序列點不僅可以使低優(yōu)先級的操作符先于高優(yōu)先級的操作符求值,而且也可以影響操作符的結(jié)合性。表達式a>b?++a:c>d?++c:++d有兩個條件操作符,條件操作符為右結(jié)合,因此,原表達式的求值順序應為(a>b?++a:(c>d?++c:++d))。左邊條件操作符的3個操作數(shù)分別為子表達式a>b,子表達式++a和子表達式c>d?++c:++d。按照結(jié)合性,子表達式(c>d?++c:++d)應先求值,但是,由于左邊條件操作符的問號處?有序列點,故子表達式a>b先求值,如果其為真,則只會對子表達式++a求值,而不會再對子表達式c>d?++c:++d求值了。顯然,此表達式中條件操作符的右結(jié)合只用于確認左邊條件操作符的右操作數(shù),而沒有使得右邊條件操作符先求值。
[1]InternationalOrganization forStandardization.ISO/IEC9899:1999.
[2]InternationalOrganization forStandardization.ISO/IEC9899:1990.
[3]國家技術(shù)監(jiān)督局.GB/T15272-1994程序設計語言C[R].中國標準出版社,1994.
[4]周二強.C語言內(nèi)涵教程[M].北京:中國鐵道出版社,2013.