預防錯誤的做法是採取積極的預防措施。本節將給出一些有益的建議。
最簡單的預防措施莫過於使用正確的書寫格式,這既可以避免錯誤,又能給查錯提供方便。為了提高可讀性,應增加必要的註釋。其實,註釋既能幫助理解程序,也能為查錯提供方便,所以不要小看它的作用。
13.1.1 書寫格式和注意事項
C語言的書寫格式對於充分理解這種語言非常重要。一個格式適當的程序和一個格式不適當的程序就像一封寫得很漂亮的信和一封寫得非常凌亂的信,給人的印象是大不一樣的。書寫程序時,應該使源代碼易於理解,特別是容易被輸入這些程序的程序員所理解,這有助於複雜程序的調試及以前輸入代碼的修改。
教科書為了減少頁碼,都盡可能地減少空行。再加上每行字數有限,為了在一行排下一條長的語句,也盡可能不用空格(包括作者本人,也是這樣處理的,這都是無奈之舉)。程序員在寫程序時,一定不要再受書的影響。常常聽同學講:「我是按照xxx書的格式寫的」,或「我是按照xxx人的格式寫的」,這些思想都是要不得的,應該根據實際情況來處理這些問題,不能生搬硬套。尤其是不要拿不正確的做法作為自己的借口。
下面說的書寫風格,是指在編程環境裡編寫程序的風格。其實,編程環境會根據語句自動處理對齊和縮進。例如碰到if語句,換行時就會自動縮進。
提倡使用縮進式和必要的空行的書寫風格,這樣可使源代碼具有層次性和邏輯性,增加程序的可讀性和可操作性。
一般來講,每次縮進5個字符的位置,並按程序特性設置空行。讀者在編寫程序時,應注意養成良好的書寫風格。
1.建議的書寫格式
在書寫程序語句時,一般應注意如下規則。
(1)括號緊跟在函數名的後面,但在for和while後面,應用一個空格與左括號隔開以增加可讀性。
(2)數學運算符的左右各留一個空格以與表達式區別。
(3)在聲明多個參數時,逗號後面留一個空格。
(4)在if、for、do…while和while語句中,合理使用縮進、一對花括號和空行。
(5)在if…else之類的語句及其嵌套語句中,注意書寫格式要易於排錯並提高可讀性。
(6)在碰到if和for等語句的組合,或for嵌套時,不僅要縮進,而且一定要留有空行,突出它們的層次。為了避免混淆,可以增加必要的註釋。
(7)函數名和「(」之間留一空格,參數之間不要擠在一起,各個參數之間至少在「,」號後面留一空格。為了易於理解,函數原型聲明時,可以包括參數類型和參數名。
(8)在帶有運算符的表達式中,可在運算符兩側留一個空格。對像「++」一類的敏感運算符,一定要表達清楚自己的意圖。
(9)對複雜的表達式,合理使用括號以免計算順序出錯。
第25章的部分程序將遵循這些格式。
2.注意集成環境對字體的顯示顏色
各個編譯環境對關鍵字和註釋都配以特定顏色,所以在書寫時要注意它們的顏色是否與規定的相符。拼錯關鍵字或者混有中文空格,都會使它們的顏色變為普通顏色。註釋前面混有中文空格,也會變為普通顏色。
3.書寫程序的參考建議
採用一些書寫方式可以避免某些錯誤,這裡給出幾點建議。
(1)書寫程序時,輸入西文要在西文狀態下進行,尤其是「,」和「;」號,「=」和「!」之類的運算符也要注意。
(2)程序中的「{」和「}」是配對出現的。一般在if、for、while等復合語句中,如果需要使用括號,可以在書寫時就把一對括號寫出來,以避免漏掉右括號。
(3)switch語句要求一對括號,使用時就先把一對括號寫出。
(4)主函數是int類型,一開始就寫出包含語句及它的主體部分,即
#include <stdio.h> int main (void ) { return 0 ; }
(5)函數定義也採取與主函數相同的方法,一定要把函數類型和參數定義正確。
(6)結構等的定義不要漏掉右括號外邊的分號,最好在定義時先把括號和分號寫出。先寫出框架,然後再往裡面添內容。例如:
struct student { } ;
(7)對可能存在歧義的地方要採取措施。例如「x=-5」,為避免一些版本誤解為「x=x-5」,可以寫成「x=(-5)」或空格多一點,如「x=-5」。
(8)在編寫程序時,對少量吃不準的地方,可以先使用「//」或「/**/」註釋掉,通過調試進行取捨。對大量需要修改的部分,可以複製備份,用「#if 0--#endif」的方式先註釋起來,以免修改後不滿意,還要恢復到原狀。
如果書寫程序時就能避免一些錯誤,將會起到事半功倍的效果。
13.1.2 命名注意事項
在程序中要為常量和變量起個合適的名字,函數名也是如此。同理,教科書都是採取很簡單的命名方式,在編程中不要學習這種做法。正確地命名有助於程序的查錯和理解。
在C語言中,大小寫字母具有不同的含義,如name和NAME就代表不同的標識符。原來的C語言中雖然規定標識符的長度不限,但只有前8個字符有效,所以對下面兩個變量是無法區別的。
dwNumberRadio dwNumberTV
現在流行的32位操作系統配備的C編譯器已經能識別長文件名,不再受8位的限制。另外,在取名時不僅要保證正確性,還要考慮容易區分,不易混淆。例如,數字1和字母i在一起,就不易辨認。取名時應使名字有很清楚的含義。例如,使用area作為求面積函數的名字,area的英文含義就是面積,就很容易從名字猜出函數的功能。對一個可讀性好的程序,必須選擇恰當的標識符,取名應統一規範,使讀者一目瞭然。
1.常量命名
使用的常量都有具體的含義,所以不要用a、b、c之類的單個字母(除非是公認的常量符號,例如物理學中的比例係數k),應該能從名字知道它的含義,例如圓周率pi。名字可以長一些,以便能很容易地知道它們的用途,方便理解和維護。
為了和變量區別,常量有時用大寫字母表示,如圓周率PI。常量命名可以用漢語拼音的字頭,也可以用英文的縮寫,還可以參考下面介紹的變量命名方法,只是命名時無需考慮字節和字等標記。
2.變量和函數的命名
變量命名可以參考Windows API編程推薦的匈牙利命名法。這種命名法通過在數據和函數名中加入額外的信息,既可增進程序員對程序的理解,也方便查錯。例如:
char ch ; // 所有的字符變量均以ch 開始 byte b ; // 所有的字節變量均以b 開始 long l ; // 所有的長字變量均以l 開始
用前綴p作為定義指針的標記,則有:
char *pch ; // 指向字符變量的指針以pch 開始 byte *pb ; // 指向字節變量的指針以pb 開始 long *pl ; // 指向長字變量的指針以pl 開始 char **ppch ; // 指向字符指針的指針以ppch 開始 byte **ppb ; // 指向字節指針的指針以ppb 開始
函數、變量及數組的命名與此同理。下面的含義就非常清楚:
ch = chLastKeyPressed ; // 由變量得到一個字符 ch = chInputBuffer[i] ; // 由數組得到一個字符 ch = chReadKeyboard ( ); // 由鍵盤函數讀入一個字符
用下面的變量可以清楚地理解它們的含義:
dTVPrice // 電視機價格—double 型 dRadioPrice // 收音機價格—double 型 iBoyNumber // 男孩的人數—整型
當看到某個函數里有名為pchText的變量時,不用查看聲明,就可以知道它是指向字符的指針。如果在程序中看到向變量bOne賦值45645.65,就能判斷這是錯誤的(bOne是字節變量)。
在內部名字中至少前31個字符是有效的,所以應該採用直觀的名字。一般可以遵循如下簡單規律:
(1)使用能代表數據類型的前綴。
(2)名稱盡量接近變量的作用。
(3)如果名稱由多個英文單詞組成,每個單詞的第1個字母大寫。
(4)由於庫函數通常使用下劃線開頭的名字,因此不要將這類名字用作變量名。
(5)局部變量使用比較短的名字,尤其是循環控制變量(又稱循環位標)。
(6)外部變量使用比較長且貼近所代表變量的含義的名字。
(7)函數名字使用動詞,如Get_char(void)。變量使用名詞,如iMen_Number。
13.1.3 程序註釋
程序註釋不是愈多愈好,而是重在說明算法以有助於理解和使用。根據使用情況,可以把它分為幾種類型。
1.程序作者和版權說明
這放在程序的起始部分,說明軟件的作用、軟件的版本和作者的信息。對於公司的軟件文檔,這是不可缺少的,但作為自寫自用,又作別論。
2.函數的註釋
對於自己編寫的函數,應該在函數前以註釋的形式給予說明。一般包括函數功能、參數和返回值。下面是一個簡單的例子。
/******************************************/ /* 函數int max (int number1 , int number2 )*/ /* 功能:求兩個整數中的大者 */ /* 參數number1 :整數 */ /* number2 : 整數 */ /* 返回值:整數 */ /******************************************/
當然,對於容易理解的函數,也可以簡單地給予解釋。例如:
// 函數max 用於求兩個整數中的大者
甚至在函數原型聲明時予以註釋。例如:
int max (int number1 , int number2 ); // 求兩個整數中的大者
可以根據情況選擇,甚至不予註釋(例如max函數的名字已經顯示出它的作用),但對較為複雜的函數,建議予以詳細註釋。
3.變量和常量的註釋
儘管遵循匈牙利命名法可以使人容易理解名字的含義,但不能全部寄托在別人的理解上,所以對一些關鍵常量和變量,仍然需要給予註釋。例如:
#define MaxSize 64 // 定義一維數組的最大長度 const int length = 2 ; // 步長為2 int mul_total = 1 ; // 存放乘積結果,初始值為1
4.程序語句的註釋
為了便於維護,需要對一些語句的作用進行註釋,包括初始值和歸零等語句。尤其是一個變量被另外一個程序段使用時,更應該交代清楚其來龍去脈,以免誤解。
sum = 2+ count ++ ; // 將計數器加1 後,再與2 相加並計入總數 count = 0 ; // 清零,準備重新計數
5.算法段的註釋
對重要的算法段進行註釋,不僅是為了提高可讀性,也是為了驗證算法的正確性。有時,通過對它們進行註釋,會找到優化或改進的辦法。
在編程時,有時甚至是先寫出詳細註釋,再來編程實現。有時會通過反覆修改註釋,找到最優的實現方法。例如:
//state=0 時,等待input=1 ,置state=1 else if ((state==0 )&& (input==1 )) { state=1 ; //input=1 ,state 從0 變到1 p=&buf[i] ; // 單詞起點 }
本書將在第25章用實例說明這一問題。
6.頭文件的註釋
頭文件涉及變量和函數等的聲明,忌諱羅列一堆常量、變量和函數原型,讓人不知道它們是幹什麼的。應該給予必要的註釋,既增進可讀性,也方便在需要時能很快找到相應文件去查詢。下面是一個頭文件的部分示例。
/***************************** * 建立頭文件 * *****************************/ #if !defined (RING_H ) #define RING_H struct person // 參加人的數據結構 { char name[10] ; // 參加人的名字或代號 struct person *next ; } ; …… // 省略 void Countx (int m ); // 數間隔數 void Dispx (); // 顯示出局者 void Clsx (); // 刪除出局的結點 void SetRing (int n ); // 建立循環鏈表 void Find (); // 求解 void Initial (); // 接收遊戲的人數和間隔數 void FindOut (); // 找出並輸出出局者 void PrintLeft (); // 輸出存活者 #endif