一時糊涂了,請問,if(temp--==0) 請問是先用temp也0比較之后再減,還是先減后比較,
網友評論:最好不要這樣寫啊
分成2句寫是最好的
不然,你會后悔的!
網友評論:教科書是比較詳細,實際中我還沒有真正用到這個方式
網友評論:先比較再減
網友評論:不一定的,我想即然有這種語法的存在,肯定有人會用的.現在大家不是記住了,以后也會用了么
網友評論:哈哈哈,就是啊,還是不要這么寫了。。。看著就暈。
不過++和--的優先級很低。。。。
:)
fengfeng 發表于 2009-8-2 21:41
你這就說錯了,++,--是單目操作符,比==高,優先級很高的來,排行第二,從右到左結合,
這里相當于(temp--)==0,只不過--在后面是先運算后自減。
網友評論:好象有爭議的東西都是好東西一樣,有空看下高質量程序設計。有些東西并不是優點。
網友評論:先判斷,再減
但是這樣編寫的代碼會給自己調試和維護帶來麻煩,而且不符合編碼規范
網友評論:teep--:先比較,再減;
--teep:先減,在比較。
網友評論:寫出這樣的代碼來實在該打PP
網友評論:調試一下
網友評論:這不是自找麻煩嗎
網友評論:括號的優先級高 自加自減都比較低了 這個肯定是先比再減
網友評論:++和--是看在變量的哪邊,左邊的話,先
計算在判斷的,在右邊的話是先判斷再計算
網友評論:++ 和-- 的首先級別是最高的,比括號還高,
如果 X =5;y =(x++) *(x--);那么,結果y就等于 25
如果 X =5;y =(++x) *(x);那么,結果y就等于 30
網友評論:是的,LS理解正確。
網友評論:先判斷
網友評論:存在必有其合理性,如果用的方便就用
網友評論:k&r 里就這么寫。。。。
一個不錯的總結。
-----------------------------------------------------------------------------------------
3. Side Effect與Sequence Point 請點評
如果你只想規規矩矩地寫代碼,那么基本用不著看這一節。本節的內容基本上是鉆牛角尖兒的,除了Short-circuit比較實用,其它寫法都應該避免使用。但沒辦法,有時候不是你想鉆牛角尖兒,而是有人逼你去鉆牛角尖兒。這是我們的學員在找工作筆試時碰到的問題:
int a=0;
a = (++a)+(++a)+(++a)+(++a);
據我了解,似乎很多公司都有出這種筆試題的惡趣味。答案應該是Undefined,我甚至有些懷疑出題人是否真的知道答案。下面我來解釋為什么是Undefined。
我們知道,調用一個函數可能產生Side Effect,使用某些運算符(++ -- = 復合賦值)也會產生Side Effect,如果一個表達式中隱含著多個Side Effect,究竟哪個先發生哪個后發生呢?C標準規定代碼中的某些點是Sequence Point,當執行到一個Sequence Point時,在此之前的Side Effect必須全部作用完畢,在此之后的Side Effect必須一個都沒發生。至于兩個Sequence Point之間的多個Side Effect哪個先發生哪個后發生則沒有規定,編譯器可以任意選擇各Side Effect的作用順序。下面詳細解釋各種Sequence Point。
1、調用一個函數時,在所有準備工作做完之后、函數調用開始之前是Sequence Point。比如調用foo(f(), g())時,foo、f()、g()這三個表達式哪個先求值哪個后求值是Unspecified,但是必須都求值完了才能做最后的函數調用,所以f()和g()的Side Effect按什么順序發生不一定,但必定在這些Side Effect全部作用完之后才開始調用foo函數。
2、條件運算符?:、逗號運算符、邏輯與&&、邏輯或||的第一個操作數求值之后是Sequence Point。我們剛講過條件運算符和逗號運算符,條件運算符要根據表達式1的值是否為真決定下一步求表達式2還是表達式3的值,如果決定求表達式2的值,表達式3就不會被求值了,反之也一樣,逗號運算符也是這樣,表達式1求值結束才繼續求表達式2的值。
邏輯與和邏輯或早在第 3 節 “布爾代數”就講了,但在初學階段我一直回避它們的操作數求值順序問題。這兩個運算符和條件運算符類似,先求左操作數的值,然后根據這個值是否為真,右操作數可能被求值,也可能不被求值。比如例 8.5 “剪刀石頭布”這個程序中的這幾句:
ret = scanf("%d", &man);
if (ret != 1 || man < 0 || man > 2) {
printf("Invalid input!\n");
return 1;
}
其實可以寫得更簡單(類似于[K&R]的簡潔風格):
if (scanf("%d", &man) != 1 || man < 0 || man > 2) {
printf("Invalid input!\n");
return 1;
}
這個控制表達式的求值順序是:先求scanf("%d", &man) != 1的值,如果scanf調用失敗,則返回值不等于1成立,||運算有一個操作數為真則整個表達式為真,這時直接執行下一句printf,根本不會再去求man < 0或man > 2的值;如果scanf調用成功,則讀入的數保存在變量man中,并且返回值等于1,那么說它不等于1就不成立了,第一個||運算的左操作數為假,就會去求右操作數man < 0的值作為整個表達式的值,這時變量man的值正是scanf讀上來的值,我們判斷它是否在[0, 2]之間,如果man < 0不成立,則整個表達式scanf("%d", &man) != 1 || man < 0 的值為假,也就是第二個||運算的左操作數為假,所以最后求右操作數man > 2的值作為整個表達式的值。
&&運算與此類似,a && b的計算過程是:首先求表達式a的值,如果a的值是假則整個表達式的值是假,不會再去求b的值;如果a的值是真,則下一步求b的值作為整個表達式的值。所以,a && b相當于“if a then b”,而a || b相當于“if not a then b”。這種特性稱為Short-circuit,很多人喜歡利用Short-circuit特性簡化代碼。
3、在一個完整的聲明末尾是Sequence Point,所謂完整的聲明是指這個聲明不是另外一個聲明的一部分。比如聲明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。
4、在一個完整的表達式末尾是Sequence Point,所謂完整的表達式是指這個表達式不是另外一個表達式的一部分。所以如果有f(); g();這樣兩條語句,f()和g()是兩個完整的表達式,f()的Side Effect必定在g()之前發生。
5、在庫函數即將返回時是Sequence Point。這條規則似乎可以包含在上一條規則里面,因為函數返回時必然會結束掉一個完整的表達式。而事實上很多庫函數是以宏定義的形式實現的(第 2.1 節 “函數式宏定義”),并不是真正的函數,所以才需要有這條規則。
還有兩種Sequence Point和某些C標準庫函數的執行過程相關,此處從略,有興趣的讀者可參考[C99]的Annex C。
現在可以分析一下本節開頭的例子了。a = (++a)+(++a)+(++a)+(++a);的結果之所以是Undefined,因為在這個表達式中有五個Side Effect都在改變a的值,這些Side Effect按什么順序發生不一定,只知道在整個表達式求值結束時一定都發生了。比如現在求第二個++a的值,這時第一個、第三個、第四個++a的Side Effect發生了沒有,a的值被加過幾次了,這些都不確定,所以第二個++a的值也不確定。這行代碼用不同平臺的不同編譯器來編譯結果是不同的,甚至在同一平臺上用同一編譯器的不同版本來編譯也可能不同。
寫表達式應遵循的原則一:在兩個Sequence Point之間,同一個變量的值只允許被改變一次。僅有這一條原則還不夠,例如a[i++] = i;的變量i只改變了一次,但結果仍是Undefined,因為等號左邊改i的值,等號右邊讀i的值,到底是先改還是先讀?這個讀寫順序是不確定的。但為什么i = i + 1;就沒有歧義呢?雖然也是等號左邊改i的值,等號右邊讀i的值,但你不讀出i的值就沒法計算i + 1,那拿什么去改i的值呢?所以這個讀寫順序是確定的。寫表達式應遵循的原則二:如果在兩個Sequence Point之間既要讀一個變量的值又要改它的值,只有在讀寫順序確定的情況下才可以這么寫。
-----------------------------------------------------------------------------------------
4. 運算符總結
到此為止,除了和指針相關的運算符還沒講之外,其它運算符都講過了,是時候做一個總結了。
運算符+ - * / % > < >= <= == != & | ^ 以及各種復合賦值運算符要求兩邊的操作數類型一致,條件運算符?:要求后兩個操作數類型一致,這些運算符在計算之前都需要做Usual Arithmetic Conversion。
下面按優先級從高到低的順序總結一下C語言的運算符,每一條所列的各運算符具有相同的優先級,對于同一優先級的多個運算符按什么順序計算也有說明,雙目運算符就簡單地用“左結合”或“右結合”來說明了。和指針有關的運算符* & ->也在這里列出來了,到第 23 章 指針再詳細解釋。
1、標識符、常量、字符串和用()括號套起來的表達式是組成表達式的最基本單元,在運算中做操作數,優先級最高。
2、后綴運算符,包括數組取下標[]、函數調用()、結構體取成員“.”、指向結構體的指針取成員->、后綴自增++、后綴自減--。如果一個操作數后面有多個后綴,按照離操作數從近到遠的順序(也就是從左到右)依次計算,比如a.name++,先算a.name,再++,這里的.name應該看成a的一個后綴,而不是把.看成雙目運算符。
3、單目運算符,包括前綴自增++、前綴自減--、sizeof、類型轉換()、取地址運算&、指針間接尋址*、正號+、負號-、按位取反~、邏輯非!。如果一個操作數前面有多個前綴,按照離操作數從近到遠的順序(也就是從右到左)依次計算,比如!~a,先算~a,再求!。
4、乘*、除/、模%運算符。這三個運算符是左結合的。
5、加+、減-運算符。左結合。
6、移位運算符<<和>>。左結合。
7、關系運算符< > <= >=。左結合。
8、相等性運算符==和!=。左結合。
9、按位與&。左結合。
10、按位異或^。左結合。
11、按位或|。左結合。
12、邏輯與&&。左結合。
13、邏輯或||。左結合。
14、條件運算符:?。在第 2 節 “if/else語句”講過Dangling-else問題,條件運算符也有類似的問題。例如a ? b : c ? d : e是看成(a ? b : c) ? d : e還是a ? b : (c ? d : e)呢?C語言規定是后者。
15、賦值=和各種復合賦值(*= /= %= += -= <<= >>= &= ^= |=)。在雙目運算符中只有賦值和復合賦值是右結合的。
16、逗號運算符。左結合。
[K&R]第2章也有這樣一個列表,但是對于結合性解釋得不夠清楚。左結合和右結合這兩個概念只對雙目運算符有意義,對于前綴、后綴和三目運算符我單獨做了說明。C語言表達式的詳細語法規則可以參考[C99]的Annex A.2,其實語法規則并不是用優先級和結合性這兩個概念來表述的,有一些細節用優先級和結合性是表達不了的,只有看C99才能了解完整的語法規則。
網友評論:剛學C語言的時候,喜歡縮寫。
現在反而不喜歡縮寫了。寧可多寫幾句,加個括號等看似麻煩的事。
網友評論:看不懂的寫法,不是好寫法。
程序匠人 發表于 2009-8-4 00:25
頂
網友評論:多用括號,除非學校的考試
網友評論:很多人被教科書蒙騙了,書上只是用這個寫法來說明運算符的優先級,而實際應用時要看編譯器怎么做,比較煩。
網友評論:這種寫法的功能是用來開闊思路,編程時不用為好
網友評論:應該是先判斷啦,從右到左,不過連我都不會這樣寫的
網友評論:簡單即是美~~
如果我們是做工程的,而不是做學術的,建議記住以下“三不”:
不要挑戰自己的記性;
不要挑戰同事的耐心;
不要挑戰編譯器的水平。
網友評論:一個記憶這個問題的小竅門:如果++或--在變量的前面,表示先做遞加或遞減運算并回存結果,再用遞加或遞減結果參與其它的計算;如果++或--在變量的后面,則先使用變量的數值參與其它的計算,隨后再把遞加或遞減結果存回這個變量。
使用++或--的好處是可以使程序簡練。如果樓主的問題不這樣寫,而是分開寫,則是這樣:
if(temp==0){
temp--;
......
}
else {
temp--;
......
}
使用--,則可以這樣寫:
if(temp--==0){
......
}
else {
......
}
估計當初是為了某種優化指令而設計了這2個操作。
網友評論:根據c語言的用法應該就是先判斷temp是否等于0再進行自減
網友評論:很多人的心聲啊,呵呵,如果考試了,講技巧還差不多
實際應用中,講的是實用,通俗易懂。
不過C語言中有講,“temp--”這一句代碼中,temp的值將不改變。只有執行完這段代碼
后,temp的值才減1,所以------應該是先判斷=0,執行完if語句之后,temp的值才減1。
如果是想先減1再判斷=0的話,可以if(--temp == 0 )
但是誰會去做不保險的事呢,所以寧可if((temp--) == 0)加多個括號,也不玩弄技巧!
網友評論:
掌握基本概念就好理解了。以前咱也搞不清楚,還總抱怨搞不清誰的優先級高,今天終于掌握了“訣竅”。
剛學的基本概念,現學現賣:
“i ++ 表示先使用 i 的值,然后++。”(呵呵,什么叫“先使用 i 的值”?)
“++ i 表示先++ ,然后使用 i的值。”
所以,LZ的問題不用實驗,肯定是16樓說的“先判斷,然后減。”(先使用temp的值,后考慮++ 。)
網友評論:我肯定會這么寫if(temp-- == 0),而不會寫成像51樓香版所展開的那樣,想必香版也不會把它展開寫吧。
if(temp-- == 0)才是正真講效率的寫法(特別在嵌入式軟件里),不管是對閱讀者還是對編譯器來說,都是有好處的。
首先是閱讀者,高手是肯定看不慣展開的寫法,當他/她看到if表達式后出現了一個語句temp--,而在else后又出現了語句temp--,他/她會覺得非常不舒服:語句(表達式)有冗余,這明明可以合成一步放在if表達式中。有TX要拍磚了:他/她為什么會覺得不舒服?他/她也有可能喜歡冗余啊。我下面會解釋的(自己先納悶一下:!@#$%^,有人喜歡冗余?)。
其二,編譯器,在年代久遠的時候,那時候整個編譯優化技術水平還相當低下,對這種展開的寫法基本無法優化(表達式跨邏輯合并),編譯結果就是兩個temp--都編譯出來了(實際的匯編指令),但使用者希望兩個表達式合并,只編譯出一個。這要求相當合理。C語言的創造者認為,既然編譯器做不到這種優化,那么就引進一種語法,讓使用者自己去合并表達式。這就是++、--的由來。這樣一來,編譯器也省心了,來一個表達式,編譯一個表達式,來兩個表達式,就編譯兩個表達式。
雖然當前的編譯優化技術發展到了一個很高的高度,但想要完全扔掉++、--而不損失一點效率,還是不行的。
在現實中,有的人不懈地追求效率(這是一個人生態度),他們在算法、程序設計方面也是如此。一旦有一個語法(或者一個算法)可以讓他們的程序提高效率,他們是無論如何也不會拒絕的,而只會努力熟悉它、掌握它,不讓它產生負面影響。那么,可以想象,日積月累,形成習慣以后,如果再讓他們去看那種冗余的編程寫法,他們是多么的不舒服!也可以想象,這些人憑著他們精益求精的態度,必定都會有所成就。
本貼中反對使用++、--的TX,難道你們認為自己水平高過那些創造++、--和熟練使用++、--的大牛們嗎?
附帶描述一下if(temp--==0)最優化的編譯結果:
1、減法指令:temp -= 1。說明:由于做了減法,如果之前temp的值為0,那么狀態
寄存器的C標志置1,否則C標志清0。(這步在大多數CPU上只需要1條指令)
2、條件分枝指令:如果C == 0,則跳轉到else。(這步在大多數CPU上只需要1條指令)
TX們,看看吧,不用--,能達到這么高的效率嗎?而且,編譯結果是不是像你們想象的那樣先比較,再做減法,然后跳轉呢?
網友評論:++ 和-- 的首先級別是最高的,比括號還高,
如果 X =5;y =(x++) *(x--);那么,結果y就等于 25
如果 X =5;y =(++x) *(x);那么,結果y就等于 30 ...
呆板書生 發表于 2009-9-23 10:07
這個例子不能說明++/--比()優先級高,僅僅說明++在前與在后的區別而已.++/--與()優先級同等
網友評論:支持51L
網友評論:強烈建議不要這么寫!
我很贊同有幾樓說的:這種東西只是在考試的時候會用,只有教科書上喜歡這樣寫(可能是說明優先級的必要吧)。
事實上這樣的寫法不但沒有技巧,反而很容易弄錯,也不便于閱讀。其實吧,我現在覺得對于程序來說,結構清晰最重要了,代碼的繁簡關鍵要看你的思路和算法!
網友評論:考試過客解答
++i,--i(在使用i之前,先使i得值加(減)1)
i++,i--(在使用i之后,使i得值加(減)1)
所以LZ所問為先比較后自減1
網友評論:哦 是這樣的我還沒有用過--我只用過++
網友評論:哪先哪后并不重要,不知道也不為過,重要的是不要這樣寫
網友評論:看不懂的語法不是好語法~~
網友評論:我覺得不對吧應該加()的 不然怎么執行啊temp-- 是先判斷再減
網友評論:哈哈 跟著 你們學習一下
網友評論:路過