28、||和&&的語句執行順序
————————————
條件語句中的這兩個「與」和「或」操作符一定要小心,它們的表現可能和你想像的不一樣,這裡條件語句中的有些行為需要和說一下:
express1 || express2
先執行表示式express1如果為「真」,express2將不被執行,express2僅在express1為「假」時才被執行。因為第乙個表示式為真了,整個表示式都為真,所以沒有必要再去執行第二個表示式了。
express1 && express2
先執行表示式express1如果為「假」,express2將不被執行,express2僅在express1為「真」時才被執行。因為第乙個表示式為假了,整個表示式都為假了,所以沒有必要再去執行第二個表示式了。
於是,他並不是你所想像的所有的表示式都會去執行,這點一定要明白,不然你的程式會出現一些莫明的執行時錯誤。
例如,下面的程式:
if ( sum > 100 &&
( ( fp=fopen( filename,"a" ) ) != null )
fprintf( fp, " sum is %id \n", sum );
fclose( fp );
本來的意圖是,如果sum > 100 ,向檔案中寫一條出錯資訊,為了方便,把兩個條件判斷寫在一起,於是,如果sum<=100時,開啟檔案的操作將不會做,最後,fprintf和fclose就會發現未知的結果。
再比如,如果我想判斷乙個字元是不是有內容,我得判斷這個字串指標是不為空(null)並且其內容不能為空(empty),乙個是空指標,乙個是空內容。我也許會這樣寫:
if ( ( p != null ) && ( strlen(p) != 0 ))
於是,如果p為null,那麼strlen(p)就不會被執行,於是,strlen也就不會因為乙個空指標而「非法操作」或是乙個「core dump」了。
記住一點,條件語句中,並非所有的語句都會執行,當你的條件語句非常多時,這點要尤其注意。
29、盡量用for而不是while做迴圈
———————————————
基本上來說,for可以完成while的功能,我是建議盡量使用for語句,而不要使用while語句,特別是當迴圈體很大時,for的優點一下就體現出來了。
因為在for中,迴圈的初始、結束條件、迴圈的推進,都在一起,一眼看上去就知道這是乙個什麼樣的迴圈。剛出學校的程式一般對於鏈結喜歡這樣來:
p = phead;
while ( p )
當while的語句塊變大後,你的程式將很難讀,用for就好得多:
for ( p=phead; p; p=p->next )
一眼就知道這個迴圈的開始條件,結束條件,和迴圈的推進。大約就能明白這個迴圈要做個什麼事?而且,程式維護進來很容易,不必像while一樣,在乙個編輯器中上上下下的搗騰。
30、請sizeof型別而不是變數
—————————————
許多程式設計師在使用sizeof中,喜歡sizeof變數名,例如:
int score[100];
char filename[20];
struct userinfo usr[100];
在sizeof這三個的變數名時,都會返回正確的結果,於是許多程式設計師就開始sizeof變數名。這個習慣很雖然沒有什麼不好,但我還是建議sizeof型別。
我看到過這個的程式:
pscore = (int*) malloc( subject_cnt );
memset( pscore, 0, sizeof(pscore) );
...此時,sizeof(pscore)返回的就是4(指標的長度),不會是整個陣列,於是,memset就不能對這塊記憶體進行初始化。為了程式的易讀和易維護,我強烈建議使用型別而不是變數,如:
對於score: sizeof(int) * 100 /* 100個int */
對於filename: sizeof(char) * 20 /* 20個char */
對於usr: sizeof(struct userinfo) * 100 /* 100個userinfo */
這樣的**是不是很易讀?一眼看上去就知道什麼意思了。
另外一點,sizeof一般用於分配記憶體,這個特性特別在多維陣列時,就能體現出其優點了。如,給乙個字串陣列分配記憶體,
/* * 分配乙個有20個字串,
* 每個字串長100的記憶體
*/
char* *p;
/** 錯誤的分配方法
*/p = (char**)calloc( 20*100, sizeof(char) );
/** 正確的分配方法
*/p = (char**) calloc ( 20, sizeof(char*) );
for ( i=0; i<20; i++)
(注:上述語句被注釋掉的是原來的,是錯誤的,由dasherest朋友指正,謝謝)
為了**的易讀,省去了一些判斷,請注意這兩種分配的方法,有本質上的差別。
31、不要忽略warning
——————————
對於一些編譯時的警告資訊,請不要忽視它們。雖然,這些warning不會妨礙目標**的生成,但這並不意味著你的程式就是好的。必竟,並不是編譯成功的程式才是正確的,編譯成功只是萬里長征的第一步,後面還有大風大浪在等著你。從編譯程式開始,不但要改正每個error,還要修正每個warning。這是乙個有修養的程式設計師該做的事。
一般來說,一面的一些警告資訊是常見的:
1)宣告了未使用的變數。(雖然編譯器不會編譯這種變數,但還是把它從源程式中注釋或是刪除吧)
2)使用了隱晦宣告的函式。(也許這個函式在別的c檔案中,編譯時會出現這種警告,你應該這使用之前使用extern關鍵字宣告這個函式)
3)沒有轉換乙個指標。(例如malloc返回的指標是void的,你沒有把之轉成你實際型別而報警,還是手動的在之前明顯的轉換一下吧)
4)型別向下轉換。(例如:float f = 2.0; 這種語句是會報警告的,編譯會告訴你正試圖把乙個double轉成float,你正在閹割乙個變數,你真的要這樣做嗎?還是在2.0後面加個f吧,不然,2.0就是乙個double,而不是float了)
不管怎麼說,編譯器的warning不要小視,最好不要忽略,乙個程式都做得出來,何況幾個小小的warning呢?
32、書寫debug版和release版的程式
————————————————
程式在開發過程中必然有許多程式設計師加的除錯資訊。我見過許多專案組,當程式開發結束時,發動群眾刪除程式中的除錯資訊,何必呢?為什麼不像vc++那樣建立兩個版本的目標**?乙個是debug版本的,乙個是release版的。那些除錯資訊是那麼的寶貴,在日後的維護過程中也是很寶貴的東西,怎麼能說刪除就刪除呢?
利用預編譯技術吧,如下所示宣告除錯函式:
#ifdef debug
void trace(char* fmt, ...)
#else
#define trace(char* fmt, ...)
#endif
於是,讓所有的程式都用trace輸出除錯資訊,只需要在在編譯時加上乙個引數「-ddebug」,如:
cc -ddebug -o target target.c
於是,預編譯器發現debug變數被定義了,就會使用trace函式。而如果要發布給使用者了,那麼只需要把取消「-ddebug」的引數,於是所有用到trace巨集,這個巨集什麼都沒有,所以源程式中的所有trace語言全部被替換成了空。一舉兩得,一箭雙鵰,何樂而不為呢?
順便提一下,兩個很有用的系統巨集,乙個是「__file__」,乙個是「__line__」,分別表示,所在的原始檔和行號,當你除錯資訊或是輸出錯誤時,可以使用這兩個巨集,讓你一眼就能看出你的錯誤,出現在哪個檔案的第幾行中。這對於用c/c++做的大工程非常的管用。
綜上所述32條,都是為了三大目的——
1、程式**的易讀性。
2、程式**的可維護性,
3、程式**的穩定可靠性。
有修養的程式設計師,就應該要學會寫出這樣的**!這是任何乙個想做程式設計高手所必需面對的細小的問題,程式設計高手不僅技術要強,基礎要好,而且最重要的是要有「修養」!
好的軟體產品絕不僅僅是技術,而更多的是整個軟體的易維護和可靠性。
軟體的維護有大量的工作量花在**的維護上,軟體的upgrade,也有大量的工作花在**的組織上,所以好的**,清淅的,易讀的**,將給大大減少軟體的維護和公升級成本。
程式設計修養 七
28 和 的語句執行順序 條件語句中的這兩個 與 和 或 操作符一定要小心,它們的表現可能和你想像的不一樣,這裡條件語句中的有些行為需要和說一下 express1 express2 先執行表示式express1如果為 真 express2將不被執行,express2僅在express1為 假 時才被...
程式設計修養(七)
28 和 的語句執行順序 條件語句中的這兩個 與 和 或 操作符一定要小心,它們的表現可能和你想像的不一樣,這裡條件語句中的有些行為需要和說一下 express1 express2 先執行表示式express1如果為 真 express2將不被執行,express2僅在express1為 假 時才被...
程式設計修養(七)
28 和 的語句執行順序 條件語句中的這兩個 與 和 或 操作符一定要小心,它們的表現可能和你想像的不一樣,這裡條件語句中的有些行為需要和說一下 express1 express2 先執行表示式express1如果為 真 express2將不被執行,express2僅在express1為 假 時才被...