文章目錄
C語言編程常見問題分析
C語言中的一般文法和一些編程技巧在很多書裡都講過了,下面主要講一些別的書很少講到、但是又非常重要的問題。這些都是作者在編程過程中總結出的一些經驗教訓,可以說職業程式員每天編程時都要遇到這些問題。
1.2.1 參數校正問題
在C語言的函數中,一般都要對函數的參數進行校正,但是有些情況下不在函數內進行校正,而由調用者在外部校正,到底什麼情況下應該在函數內進行校正,什麼情況下不需要在函數內進行校正呢?下列原則可供讀者參考。
1) 對於需要在大的迴圈裡調用的函數,不需要在函數內對參數進行校正。
例如鏈表的逐個遍曆函數 void *List_EnumNext(LIST *pList)。
在鏈表的逐個遍曆函數裡,要不要對pList參數的合法性進行校正呢?答案是否定的。為什麼呢?因為鏈表的逐個遍曆函數通常是要在一個迴圈裡使用的,比如一個鏈表有10 000個節點,逐個遍曆就要遍曆10 000次。如果上面的函數對參數pList進行了校正,那麼對整個鏈表的逐個遍曆過程將校正10 000次,不如由調用者在調用函數前校正一次就夠了。因此,像這種可能頻繁地被調用,且在外面校正只要校正一次就夠的函數參數是不需要在函數內部進行校正的。
2) 底層的函數調用頻度都比較高,一般不校正。
3) 對於調用頻度低的函數,參數要校正。
4) 執行時間開銷很大的函數,在參數校正相對整個函數來講占的比例可以忽略不計的情況下,一般最好對函數參數進行校正。
5) 可以大大提升軟體的穩定性時,函數參數要進行校正。
1.2.2 return語句的問題
在函數裡,使用return語句可能也是一個值得探討的問題。有些人認為應該讓函數的出口盡量的少,因此要減少return語句的使用;特別是在函數中有分配資源操作時,在之後的每個return語句裡都需要進行對應的釋放操作,在編程時很容易出現遺漏而出現資源泄漏。但是我們也知道,如果要減少return語句,又會帶來另外一些問題。首先,很多情況下要減少return語句會增加編程的難度;其次,有時候減少了return語句又使得大括弧嵌套的層數增加不少,使得程式碼長度增加不少,很容易因超過80字元而使得代碼閱讀困難。
那麼,到底什麼情況下可以減少return語句,什麼情況下又不能減少return語句呢?下列原則供讀者參考。
1) 對參數進行校正,校正失敗,一般要使用return語句,這樣做可使程式邏輯清晰,閱讀也方便,又減少了大括弧嵌套的層數。
2) 對於函數內部的不同出錯情況,要有不同的return語句;否則對外部調用者來說,無法區分出錯的真正原因。
3) 對於函數內部的同類出錯情況,盡量只使用一個return語句,即盡量不要讓兩個return語句返回相同的傳回值。
1.2.3 while迴圈和for迴圈的問題
1. 兩種迴圈在構造死迴圈時的區別
用while構造死迴圈時,一般會使用while(TRUE)來構造死迴圈;而用for來構造死迴圈時,則使用for(;;)來構造死迴圈。這兩個死迴圈的區別是:while迴圈裡的條件被看成運算式,因此,當用while構造死迴圈時,裡面的TRUE實際上被看成永遠為真的運算式,這種情況容易產生混淆,有些工具軟體如PC-Lint就會認為出錯了,因此構造死迴圈時,最好使用for(;;)來進行。
2. 兩種迴圈在普通迴圈時的區別
對一個數組進行迴圈時,一般來說,如果每輪迴圈都是在迴圈處理完後才講迴圈變數增加的話,使用for迴圈比較方便;如果迴圈處理的過程中就要將迴圈變數增加時,則使用while迴圈比較方便;還有在使用for迴圈語句時,如果裡面的迴圈條件很長,可以考慮用while迴圈進行替代,使代碼的排版格式好看一些。
1.2.4 if語句的多個判斷問題
在對參數進行校正時,經常需要校正函數的幾個參數,是對每個參數都使用一個單獨的if語句進行一次校正,還是多個語句都放在一個if語句裡用邏輯或運算來進行校正呢?還是用執行個體來說明吧。
例1-1 參數校正舉例。
TABLE *CreateTable1( int nRow,int nCol )
{
if ( nRow > MAX_ROWS )
{
return NULL;
}
if ( nCol >= MAX_COLS )
{
return NULL;
}
……
}
TABLE *CreateTable2( int nRow,int nCol )
{
if ( nRow > MAX_ROWS || nCol >= MAX_COLS )
{
return NULL;
}
……
}
在CreateTable1()和CreateTable2()兩個函數中,到底哪個寫法更好呢?答案是第二種寫法更優。為什麼呢?因為第二種寫法中,少了一個return語句,編譯後,執行代碼會比第一種寫法小,執行時耗用的記憶體自然少了,其他方面的效能都是一樣的。有興趣的讀者可以將上面兩段代碼反組譯碼出來分析一下。
1.2.5 goto語句問題
goto語句在結構化編程技術出來後,被當作破壞結構化程式的典型代表,可以說,在結構化程式設計年代,goto語句就像洪水猛獸一樣,程式員都唯恐避之不及;可後來在微軟的一些例子程式中經常把goto語句用來處理出錯,當出錯時,goto到函數要退出的一個label那裡進行資源釋放等操作。那麼,goto語句是不是只可以用於出錯處理,其他地方都不可以用了呢?下列關於使用goto語句的原則可以供讀者參考。
1) 使用goto語句只能goto到同一函數內,而不能從一個函數裡goto到另外一個函數裡。
2) 使用goto語句在同一函數內進行goto時,goto的起點應是函數內一段小功能的結束處,goto的目的label處應是函數內另外一段小功能的開始處。
3) 不能從一段複雜的執行狀態中的位置goto到另外一個位置,比如,從多重嵌套的迴圈判斷中跳出去就是不允許的。
1.2.6 switch …case 和if … else if 的效率區別
許多有關C語言的書中都說過,switch … case 的效率比if … else if 的效率高。其實這個結論並不完全正確,關鍵看實際代碼,少數情況下,if…else if寫法如果得當的話,它的效率是高於switch…case的。
例1-2 if…else if語句測試舉例。
void TestIfElse()
{
unsigned int x ;
srand( 1 ); /* 初始化隨機數發生器 */
x = rand() % 100;
if ( x == 0 )
{
……
}
else if ( x == 1 )
{
……
}
else
{
……
}
}
x為0~100範圍內的隨機數,當x不為0和1時執行else分支,因此上面函數有98%的機率是執行else分支的,而執行if分支和else if分支的機率各為1%。在執行else分支時,要先執行if判斷語句,再執行else if判斷語句後才會執行else分支裡的內容。因此執行else分支需要進行兩次判斷。如果我們按下列方式將else分支的執行改成放到if裡面去執行,則效率將大大提高。
void TestIfElse( )
{
unsigned int x ;
srand( 1 ); /* 初始化隨機數發生器 */
x = rand() % 100;
if ( x > 1 )
{
……
}
else if ( x == 0 )
{
……
}
else
{
……
}
}
如果上面的代碼用switch...case來實現,則效率不如上面的函數,讀者可以實驗一下。