編程---進(jìn)(下)11,、出錯(cuò)信息的處理
你會(huì)處理出錯(cuò)信息嗎,?哦,它并不是簡(jiǎn)單的輸出,??聪旅娴氖纠?BR> if ( p == NULL ){ printf ( "ERR: The pointer is NULLn" ); } 告別學(xué)生時(shí)代的編程吧,。這種編程很不利于維護(hù)和管理,出錯(cuò)信息或是提示信息,,應(yīng)該統(tǒng)一處理,,而不是像上面這樣,寫成一個(gè)“硬編碼”,。第10條對(duì)這方面的處理做了一部分說明,。如果要管理錯(cuò)誤信息,那就要有以下的處理: #define ERR_NO_ERROR 0 #define ERR_OPEN_FILE 1 #define ERR_SEND_MESG 2 #define ERR_BAD_ARGS 3 #define ERR_MEM_NONE 4 #define ERR_SERV_DOWN 5 #define ERR_UNKNOW_INFO 6 #define ERR_SOCKET_ERR 7 #define ERR_PERMISSION 8 #define ERR_BAD_FORMAT 9 #define ERR_TIME_OUT 10 char* errmsg[] = { "No error", "Open file error", "Failed in sending/receiving a message", "Bad arguments", "Memeroy is not enough", "Service is down; try later", "Unknow information", "A socket operation has failed", "ermission denied", "Bad configuration file format", "Communication time out", }; long errno = 0; void perror( char* info) { if ( info ){ printf("%s: %sn", info, errmsg[errno] ); return; } printf("Error: %sn", errmsg[errno] ); } 這個(gè)基本上是ANSI的錯(cuò)誤處理實(shí)現(xiàn)細(xì)節(jié)了,,于是當(dāng)你程序中有錯(cuò)誤時(shí)你就可以這樣處理: bool CheckPermission( char* userName ) { if ( strcpy(userName, "root") != 0 ){ errno = ERR_PERMISSION_DENIED; return (FALSE); } ... } main() { ... if (! CheckPermission( username ) ){ perror("main()"); } ... } 一個(gè)即有共性,,也有個(gè)性的錯(cuò)誤信息處理,這樣做有利同種錯(cuò)誤出一樣的信息,,統(tǒng)一用戶界面,,而不會(huì)因?yàn)槲募蜷_失敗,A程序員出一個(gè)信息,,B程序員又出一個(gè)信息,。而且這樣做,非常容易維護(hù),。代碼也易讀,。 當(dāng)然,物極必反,,也沒有必要把所有的輸出都放到errmsg中,,抽取比較重要的出錯(cuò)信息或是提示信息是其關(guān)鍵,但即使這樣,,這也包括了大多數(shù)的信息,。 12、常用函數(shù)和循環(huán)語句中的被計(jì)算量 看一下下面這個(gè)例子: for( i=0; i<1000; i++ ){ GetLocalHostName( hostname ); ... } GetLocalHostName的意思是取得當(dāng)前計(jì)算機(jī)名,,在循環(huán)體中,,它會(huì)被調(diào)用1000次啊。這是多么的沒有效率的事啊,。應(yīng)該把這個(gè)函數(shù)拿到循環(huán)體外,,這樣只調(diào)用一次,效率得到了很大的提高,。雖然,,我們的編譯器會(huì)進(jìn)行優(yōu)化,會(huì)把循環(huán)體內(nèi)的不變的東西拿到循環(huán)外面,但是,,你相信所有編譯器會(huì)知道哪些是不變的嗎,?我覺得編譯器不可靠。最好還是自己動(dòng)手吧,。 同樣,對(duì)于常用函數(shù)中的不變量,,如: GetLocalHostName(char* name) { char funcName[] = "GetLocalHostName"; sys_log( "%s begin......", funcName ); ... sys_log( "%s end......", funcName ); } 如果這是一個(gè)經(jīng)常調(diào)用的函數(shù),,每次調(diào)用時(shí)都要對(duì)funcName進(jìn)行分配內(nèi)存,這個(gè)開銷很大啊,。把這個(gè)變量聲明成static吧,,當(dāng)函數(shù)再次被調(diào)用時(shí),就會(huì)省去了分配內(nèi)存的開銷,,執(zhí)行效率也很好,。 13、函數(shù)名和變量名的命名 我看到許多程序?qū)ψ兞棵秃瘮?shù)名的取名很草率,,特別是變量名,,什么a,b,c,aa,bb,cc,還有什么flag1,flag2, cnt1, cnt2,,這同樣是一種沒有“修養(yǎng)”的行為,。即便加上好的注釋。好的變量名或是函數(shù)名,,我認(rèn)為應(yīng)該有以下的規(guī)則: 1) 直觀并且可以拼讀,,可望文知意,不必“解碼”,。 2) 名字的長(zhǎng)度應(yīng)該即要最短的長(zhǎng)度,,也要能最大限度的表達(dá)其含義。 3) 不要全部大寫,,也不要全部小寫,,應(yīng)該大小寫都有,如:GetLocalHostName 或是 UserAccount,。 4) 可以簡(jiǎn)寫,,但簡(jiǎn)寫得要讓人明白,如:ErrorCode -> ErrCode, ServerListener -> ServLisner,,UserAccount -> UsrAcct 等,。 5) 為了避免全局函數(shù)和變量名字沖突,可以加上一些前綴,,一般以模塊簡(jiǎn)稱做為前綴,。 6) 全局變量統(tǒng)一加一個(gè)前綴或是后綴,讓人一看到這個(gè)變量就知道是全局的,。 7) 用匈牙利命名法命名函數(shù)參數(shù),,局部變量,。但還是要堅(jiān)持“望文生意”的原則。 8) 與標(biāo)準(zhǔn)庫(kù)(如:STL)或開發(fā)庫(kù)(如:MFC)的命名風(fēng)格保持一致,。 14,、函數(shù)的傳值和傳指針 向函數(shù)傳參數(shù)時(shí),一般而言,,傳入非const的指針時(shí),,就表示,在函數(shù)中要修改這個(gè)指針把指內(nèi)存中的數(shù)據(jù),。如果是傳值,,那么無論在函數(shù)內(nèi)部怎么修改這個(gè)值,也影響不到傳過來的值,,因?yàn)閭髦凳侵粌?nèi)存拷貝,。 什么?你說這個(gè)特性你明白了,,好吧,,讓我們看看下面的這個(gè)例程: void GetVersion(char* pStr) { pStr = malloc(10); strcpy ( pStr, "2.0" ); } main() { char* ver = NULL; GetVersion ( ver ); ... ... free ( ver ); } 我保證,類似這樣的問題是一個(gè)新手最容易犯的錯(cuò)誤,。程序中妄圖通過函數(shù)GetVersion給指針ver分配空間,,但這種方法根本沒有什么作用,原因就是--這是傳值,,不是傳指針,。你或許會(huì)和我爭(zhēng)論,我分明傳的時(shí)指針???再仔細(xì)看看,其實(shí),,你傳的是指針其實(shí)是在傳值,。 15、修改別人程序的修養(yǎng) 當(dāng)你維護(hù)別人的程序時(shí),,請(qǐng)不要非常主觀臆斷的把已有的程序刪除或是修改,。我經(jīng)常看到有的程序員直接在別人的程序上修改表達(dá)式或是語句,。修改別人的程序時(shí),,請(qǐng)不要?jiǎng)h除別人的程序,如果你覺得別人的程序有所不妥,,請(qǐng)注釋掉,,然后添加自己的處理程序,必竟,你不可能100%的知道別人的意圖,,所以為了可以恢復(fù),,請(qǐng)不依賴于CVS或是SourceSafe這種版本控制軟件,還是要在源碼上給別人看到你修改程序的意圖和步驟,。這是程序維護(hù)時(shí),,一個(gè)有修養(yǎng)的程序員所應(yīng)該做的。 如下所示,,這就是一種比較好的修改方法: char* p = ( char* )calloc( 10, sizeof char ); ... 當(dāng)然,,這種方法是在軟件維護(hù)時(shí)使用的,這樣的方法,,可以讓再維護(hù)的人很容易知道以前的代碼更改的動(dòng)作和意圖,而且這也是對(duì)原作者的一種尊敬,。 以“注釋 - 添加”方式修改別人的程序,,要好于直接刪除別人的程序。 16,、把相同或近乎相同的代碼形成函數(shù)和宏 有人說,,最好的程序員,就是最喜歡“偷懶”的程序,,其中不無道理,。 如果你有一些程序的代碼片段很相似,或直接就是一樣的,,請(qǐng)把他們放在一個(gè)函數(shù)中,。而如果這段代碼不多,而且會(huì)被經(jīng)常使用,,你還想避免函數(shù)調(diào)用的開銷,,那么就把他寫成宏吧。 千萬不要讓同一份代碼或是功能相似的代碼在多個(gè)地方存在,,不然如果功能一變,,你就要修改好幾處地方,這種會(huì)給維護(hù)帶來巨大的麻煩,,所以,,做到“一改百改”,還是要形成函數(shù)或是宏,。 17,、表達(dá)式中的括號(hào) 如果一個(gè)比較復(fù)雜的表達(dá)式中,你并不是很清楚各個(gè)操作符的憂先級(jí),,即使是你很清楚優(yōu)先級(jí),,也請(qǐng)加上括號(hào),不然,別人或是自己下一次讀程序時(shí),,一不小心就看走眼理解錯(cuò)了,,為了避免這種“誤解”,還有讓自己的程序更為清淅,,還是加上括號(hào)吧,。 比如,對(duì)一個(gè)結(jié)構(gòu)的成員取地址: GetUserAge( &( UserInfo->age ) ); 雖然,,&UserInfo->age中,,->操作符的優(yōu)先級(jí)最高,但加上一個(gè)括號(hào),,會(huì)讓人一眼就看明白你的代碼是什么意思,。 再比如,一個(gè)很長(zhǎng)的條件判斷: if ( ( ch[0] >= ′0′ || ch[0] <= ′9′ ) && ( ch[1] >= ′a′ || ch[1] <= ′z′ ) && ( ch[2] >= ′A′ || ch[2] <= ′Z′ ) ) 括號(hào),,再加上空格和換行,,你的代碼是不是很容易讀懂了? 18,、函數(shù)參數(shù)中的const 對(duì)于一些函數(shù)中的指針參數(shù),,如果在函數(shù)中只讀,請(qǐng)將其用const修飾,,這樣,,別人一讀到你的函數(shù)接口時(shí),就會(huì)知道你的意圖是這個(gè)參數(shù)是[in],,如果沒有const時(shí),,參數(shù)表示[in/out],注意函數(shù)接口中的const使用,,利于程序的維護(hù)和避免犯一些錯(cuò)誤,。 雖然,const修飾的指針,,如:const char* p,,在C中一點(diǎn)用也沒有,因?yàn)椴还苣愕穆暶魇遣皇莄onst,,指針的內(nèi)容照樣能改,,因?yàn)榫幾g器會(huì)強(qiáng)制轉(zhuǎn)換,但是加上這樣一個(gè)說明,,有利于程序的閱讀和編譯,。因?yàn)樵贑中,修改一個(gè)const指針?biāo)赶虻膬?nèi)存時(shí),,會(huì)報(bào)一個(gè)Warning,。這會(huì)引起程序員的注意,。 C++中對(duì)const定義的就很嚴(yán)格了,所以C++中要多多的使用const,,const的成員函數(shù),,const的變量,這樣會(huì)對(duì)讓你的代碼和你的程序更加完整和易讀,。(關(guān)于C++的const我就不多說了) 19,、函數(shù)的參數(shù)個(gè)數(shù)(多了請(qǐng)用結(jié)構(gòu)) 函數(shù)的參數(shù)個(gè)數(shù)最好不要太多,一般來說6個(gè)左右就可以了,,眾多的函數(shù)參數(shù)會(huì)讓讀代碼的人一眼看上去就很頭昏,,而且也不利于維護(hù)。如果參數(shù)眾多,,還請(qǐng)使用結(jié)構(gòu)來傳遞參數(shù),。這樣做有利于數(shù)據(jù)的封裝和程序的簡(jiǎn)潔性。 也利于使用函數(shù)的人,,因?yàn)槿绻愕暮瘮?shù)個(gè)數(shù)很多,,比如12個(gè),調(diào)用者很容易搞錯(cuò)參數(shù)的順序和個(gè)數(shù),,而使用結(jié)構(gòu)struct來傳遞參數(shù),就可以不管參數(shù)的順序,。 而且,,函數(shù)很容易被修改,如果需要給函數(shù)增加參數(shù),,不需要更改函數(shù)接口,,只需更改結(jié)構(gòu)體和函數(shù)內(nèi)部處理,而對(duì)于調(diào)用函數(shù)的程序來說,,這個(gè)動(dòng)作是透明的,。 20、函數(shù)的返回類型,,不要省略 我看到很多程序?qū)懞瘮?shù)時(shí),,在函數(shù)的返回類型方面不太注意。如果一個(gè)函數(shù)沒有返回值,,也請(qǐng)?jiān)诤瘮?shù)前面加上void的修飾,。而有的程序員偷懶,在返回int的函數(shù)則什么不修飾(因?yàn)槿绻恍揎?,則默認(rèn)返回int),,這種習(xí)慣很不好,還是為了原代碼的易讀性,,加上int吧,。 所以函數(shù)的返回值類型,,請(qǐng)不要省略。 另外,,對(duì)于void的函數(shù),,我們往往會(huì)忘了return,由于某些C/C++的編譯器比較敏感,,會(huì)報(bào)一些警告,,所以即使是void的函數(shù),我們?cè)趦?nèi)部最好也要加上return的語句,,這有助于代碼的編譯,。 21、goto語句的使用 N年前,,軟件開發(fā)的一代宗師--迪杰斯特拉(Dijkstra)說過:“goto statment is harmful !!”,,并建議取消goto語句。因?yàn)間oto語句不利于程序代碼的維護(hù)性,。 這里我也強(qiáng)烈建議不要使用goto語句,,除非下面的這種情況: #define FREE(p) if(p) { free(p); p = NULL; } main() { char *fname=NULL, *lname=NULL, *mname=NULL; fname = ( char* ) calloc ( 20, sizeof(char) ); if ( fname == NULL ){ goto ErrHandle; } lname = ( char* ) calloc ( 20, sizeof(char) ); if ( lname == NULL ){ goto ErrHandle; } mname = ( char* ) calloc ( 20, sizeof(char) ); if ( mname == NULL ){ got- o ErrHandle; } ...... ErrHandle: FREE(fname); FREE(lname); FREE(mname); ReportError(ERR_NO_MEMOEY); } 也只有在這種情況下,goto語句會(huì)讓你的程序更易讀,,更容易維護(hù),。(在用嵌C來對(duì)數(shù)據(jù)庫(kù)設(shè)置游標(biāo)操作時(shí),或是對(duì)數(shù)據(jù)庫(kù)建立鏈接時(shí),,也會(huì)遇到這種結(jié)構(gòu)) 22,、宏的使用 很多程序員不知道C中的“宏”到底是什么意思?特別是當(dāng)宏有參數(shù)的時(shí)候,,經(jīng)常把宏和函數(shù)混淆,。我想在這里我還是先講講“宏”,宏只是一種定義,,他定義了一個(gè)語句塊,,當(dāng)程序編譯時(shí),編譯器首先要執(zhí)行一個(gè)“替換”源程序的動(dòng)作,,把宏引用的地方替換成宏定義的語句塊,,就像文本文件替換一樣。這個(gè)動(dòng)作術(shù)語叫“宏的展開” 使用宏是比較“危險(xiǎn)”的,,因?yàn)槟悴恢篮暾归_后會(huì)是什么一個(gè)樣子,。例如下面這個(gè)宏: #define MAX(a, b) a>b?a:b 當(dāng)我們這樣使用宏時(shí),沒有什么問題: MAX( num1, num2 ); 因?yàn)楹暾归_后變成 num1>num2?num1:num2,;,。但是,如果是這樣調(diào)用的,,MAX( 17+32, 25+21 ); 呢,,編譯時(shí)出現(xiàn)錯(cuò)誤,,原因是,宏展開后變成:17+32>25+21?17+32:25+21,,哇,,這是什么啊,? 所以,,宏在使用時(shí),參數(shù)一定要加上括號(hào),,上述的那個(gè)例子改成如下所示就能解決問題了,。 #define MAX( (a), (b) ) (a)>(b)?(a)b) 即使是這樣,也不這個(gè)宏也還是有Bug,,因?yàn)槿绻疫@樣調(diào)用 MAX(i++, j++); ,,經(jīng)過這個(gè)宏以后,i和j都被累加了兩次,,這絕不是我們想要的,。 所以,在宏的使用上還是要謹(jǐn)慎考慮,,因?yàn)楹暾归_是的結(jié)果是很難讓人預(yù)料的,。而且雖然,宏的執(zhí)行很快(因?yàn)闆]有函數(shù)調(diào)用的開銷),,但宏會(huì)讓源代碼澎漲,,使目標(biāo)文件尺寸變大,(如:一個(gè)50行的宏,,程序中有1000個(gè)地方用到,宏展開后會(huì)很不得了),,相反不能讓程序執(zhí)行得更快(因?yàn)閳?zhí)行文件變大,,運(yùn)行時(shí)系統(tǒng)換頁(yè)頻繁)。 因此,,在決定是用函數(shù),,還是用宏時(shí)得要小心。 23,、static的使用 static關(guān)鍵字,,表示了“靜態(tài)”,一般來說,,他會(huì)被經(jīng)常用于變量和函數(shù),。一個(gè)static的變量,其實(shí)就是全局變量,,只不過他是有作用域的全局變量,。比如一個(gè)函數(shù)中的static變量: char* getConsumerName() { static int cnt = 0; .... cnt++; .... } cnt變量的值會(huì)跟隨著函數(shù)的調(diào)用次而遞增,,函數(shù)退出后,cnt的值還存在,,只是cnt只能在函數(shù)中才能被訪問,。而cnt的內(nèi)存也只會(huì)在函數(shù)第一次被調(diào)用時(shí)才會(huì)被分配和初始化,以后每次進(jìn)入函數(shù),,都不為static分配了,,而直接使用上一次的值。 對(duì)于一些被經(jīng)常調(diào)用的函數(shù)內(nèi)的常量,,最好也聲明成static(參見第12條) 但static的最多的用處卻不在這里,,其最大的作用的控制訪問,在C中如果一個(gè)函數(shù)或是一個(gè)全局變量被聲明為static,,那么,,這個(gè)函數(shù)和這個(gè)全局變量,將只能在這個(gè)C文件中被訪問,,如果別的C文件中調(diào)用這個(gè)C文件中的函數(shù),,或是使用其中的全局(用extern關(guān)鍵字),將會(huì)發(fā)生鏈接時(shí)錯(cuò)誤,。這個(gè)特性可以用于數(shù)據(jù)和程序保密,。 24、函數(shù)中的代碼尺寸 一個(gè)函數(shù)完成一個(gè)具體的功能,,一般來說,,一個(gè)函數(shù)中的代碼最好不要超過600行左右,越少越好,,最好的函數(shù)一般在100行以內(nèi),,300行左右的孫函數(shù)就差不多了。有證據(jù)表明,,一個(gè)函數(shù)中的代碼如果超過500行,,就會(huì)有和別的函數(shù)相同或是相近的代碼,也就是說,,就可以再寫另一個(gè)函數(shù),。 另外,函數(shù)一般是完成一個(gè)特定的功能,,千萬忌諱在一個(gè)函數(shù)中做許多件不同的事,。函數(shù)的功能越單一越好,一方面有利于函數(shù)的易讀性,,另一方面更有利于代碼的維護(hù)和重用,,功能越單一表示這個(gè)函數(shù)就越可能給更多的程序提供服務(wù),也就是說共性就越多,。 雖然函數(shù)的調(diào)用會(huì)有一定的開銷,,但比起軟件后期維護(hù)來說,,增加一些運(yùn)行時(shí)的開銷而換來更好的可維護(hù)性和代碼重用性,是很值得的一件事,。 25,、typedef的使用 typedef是一個(gè)給類型起別名的關(guān)鍵字。不要小看了它,,它對(duì)于你代碼的維護(hù)會(huì)有很好的作用,。比如C中沒有bool,于是在一個(gè)軟件中,,一些程序員使用int,,一些程序員使用short,會(huì)比較混亂,,最好就是用一個(gè)typedef來定義,,如: typedef char bool; 一般來說,一個(gè)C的工程中一定要做一些這方面的工作,,因?yàn)槟銜?huì)涉及到跨平臺(tái),,不同的平臺(tái)會(huì)有不同的字長(zhǎng),所以利用預(yù)編譯和typedef可以讓你最有效的維護(hù)你的代碼,,如下所示: #ifdef SOLARIS2_5 typedef boolean_t BOOL_T; #else typedef int BOOL_T; #endif typedef short INT16_T; typedef unsigned short UINT16_T; typedef int INT32_T; typedef unsigned int UINT32_T; #ifdef WIN32 typedef _int64 INT64_T; #else typedef long long INT64_T; #endif typedef float FLOAT32_T; typedef char* STRING_T; typedef unsigned char BYTE_T; typedef time_t TIME_T; typedef INT32_T PID_T; 使用typedef的其它規(guī)范是,,在結(jié)構(gòu)和函數(shù)指針時(shí),也最好用typedef,,這也有利于程序的易讀和可維護(hù)性,。如: typedef struct _hostinfo { HOSTID_T host; INT32_T hostId; STRING_T hostType; STRING_T hostModel; FLOAT32_T cpuFactor; INT32_T numCPUs; INT32_T nDisks; INT32_T memory; INT32_T swap; } HostInfo; typedef INT32_T (*RsrcReqHandler)( void *info, JobArray *jobs, AllocInfo *allocInfo, AllocList *allocList); C++中這樣也是很讓人易讀的: typedef CArray<HostInfo, HostInfo&> HostInfoArray; 于是,當(dāng)我們用其定義變量時(shí),,會(huì)顯得十分易讀,。如: HostInfo* phinfo; RsrcReqHandler* pRsrcHand; 這種方式的易讀性,在函數(shù)的參數(shù)中十分明顯,。 關(guān)鍵是在程序種使用typedef后,,幾乎所有的程序中的類型聲明都顯得那么簡(jiǎn)潔和清淅,而且易于維護(hù),,這才是typedef的關(guān)鍵。 26,、為常量聲明宏 最好不要在程序中出現(xiàn)數(shù)字式的“硬編碼”,,如: int user[120]; 為這個(gè)120聲明一個(gè)宏吧。為所有出現(xiàn)在程序中的這樣的常量都聲明一個(gè)宏吧,。比如TimeOut的時(shí)間,,最大的用戶數(shù)量,還有其它,,只要是常量就應(yīng)該聲明成宏,。如果,,突然在程序中出現(xiàn)下面一段代碼, for ( i=0; i<120; i++){ .... } 120是什么,?為什么會(huì)是120,?這種“硬編碼”不僅讓程序很讀,而且也讓程序很不好維護(hù),,如果要改變這個(gè)數(shù)字,,得同時(shí)對(duì)所有程序中這個(gè)120都要做修改,這對(duì)修改程序的人來說是一個(gè)很大的痛苦,。所以還是把常量聲明成宏,,這樣,一改百改,,而且也很利于程序閱讀,。 #define MAX_USR_CNT 120 for ( i=0; i<MAX_USER_CNT; i++){ .... } 這樣就很容易了解這段程序的意圖了。 有的程序員喜歡為這種變量聲明全局變量,,其實(shí),,全局變量應(yīng)該盡量的少用,全局變量不利于封裝,,也不利于維護(hù),,而且對(duì)程序執(zhí)行空間有一定的開銷,一不小心就造成系統(tǒng)換頁(yè),,造成程序執(zhí)行速度效率等問題,。所以聲明成宏,即可以免去全局變量的開銷,,也會(huì)有速度上的優(yōu)勢(shì),。 27、不要為宏定義加分號(hào) 有許多程序員不知道在宏定義時(shí)是否要加分號(hào),,有時(shí),,他們以為宏是一條語句,應(yīng)該要加分號(hào),,這就錯(cuò)了,。當(dāng)你知道了宏的原理,你會(huì)贊同我為會(huì)么不要為宏定義加分號(hào)的,??匆粋€(gè)例子: #define MAXNUM 1024; 這是一個(gè)有分號(hào)的宏,如果我們這樣使用: half = MAXNUM/2; if ( num < MAXNUM ) 等等,,都會(huì)造成程序的編譯錯(cuò)誤,,因?yàn)椋?dāng)宏展開后,他會(huì)是這個(gè)樣子的: half = 1024;/2; if ( num < 1024; ) 是的,,分號(hào)也被展進(jìn)去了,,所以造成了程序的錯(cuò)誤。請(qǐng)相信我,,有時(shí)候,,一個(gè)分號(hào)會(huì)讓你的程序出現(xiàn)成百個(gè)錯(cuò)誤。所以還是不要為宏加最后一個(gè)分號(hào),,哪怕是這樣: #define LINE "=================================" #define PRINT_LINE printf(LINE) #define PRINT_NLINE(n) while ( n-- >0 ) { PRINT_LINE; } 都不要在最后加上分號(hào),,當(dāng)我們?cè)诔绦蛑惺褂脮r(shí),為之加上分號(hào),, main() { char *p = LINE; PRINT_LINE; } 這一點(diǎn)非常符合習(xí)慣,,而且,如果忘加了分號(hào),,編譯器給出的錯(cuò)誤提示,,也會(huì)讓我們很容易看懂的。 28,、||和&&的語句執(zhí)行順序 條件語句中的這兩個(gè)“與”和“或”操作符一定要小心,,它們的表現(xiàn)可能和你想像的不一樣,這里條件語句中的有些行為需要和說一下: express1 || express2 先執(zhí)行表達(dá)式express1如果為“真”,,express2將不被執(zhí)行,,express2僅在express1為“假”時(shí)才被執(zhí)行。因?yàn)榈谝粋€(gè)表達(dá)式為真了,,整個(gè)表達(dá)式都為真,,所以沒有必要再去執(zhí)行第二個(gè)表達(dá)式了。 express1 && express2 先執(zhí)行表達(dá)式express1如果為“假”,,express2將不被執(zhí)行,,express2僅在express1為“真”時(shí)才被執(zhí)行。因?yàn)榈谝粋€(gè)表達(dá)式為假了,,整個(gè)表達(dá)式都為假了,,所以沒有必要再去執(zhí)行第二個(gè)表達(dá)式了。 于是,,他并不是你所想像的所有的表達(dá)式都會(huì)去執(zhí)行,,這點(diǎn)一定要明白,不然你的程序會(huì)出現(xiàn)一些莫明的運(yùn)行時(shí)錯(cuò)誤,。 例如,,下面的程序: if ( sum > 100 && ( ( fp=fopen( filename,"a" ) ) != NULL ) { fprintf(fp, "Warring: it beyond one hundredn"); ...... } fprintf( fp, " sum is %id n", sum ); fclose( fp ); 本來的意圖是,如果sum > 100 ,,向文件中寫一條出錯(cuò)信息,為了方便,,把兩個(gè)條件判斷寫在一起,,于是,,如果sum<=100時(shí),打開文件的操作將不會(huì)做,,最后,,fprintf和fclose就會(huì)發(fā)現(xiàn)未知的結(jié)果。 再比如,,如果我想判斷一個(gè)字符是不是有內(nèi)容,,我得判斷這個(gè)字符串指針是不為空(NULL)并且其內(nèi)容不能為空(Empty),一個(gè)是空指針,,一個(gè)是空內(nèi)容,。我也許會(huì)這樣寫: if ( ( p != NULL ) && ( strlen(p) != 0 )) 于是,如果p為NULL,,那么strlen(p)就不會(huì)被執(zhí)行,,于是,strlen也就不會(huì)因?yàn)橐粋€(gè)空指針而“非法操作”或是一個(gè)“Core Dump”了,。 記住一點(diǎn),,條件語句中,并非所有的語句都會(huì)執(zhí)行,,當(dāng)你的條件語句非常多時(shí),,這點(diǎn)要尤其注意。 29,、盡量用for而不是while做循環(huán) 基本上來說,,for可以完成while的功能,我是建議盡量使用for語句,,而不要使用while語句,,特別是當(dāng)循環(huán)體很大時(shí),for的優(yōu)點(diǎn)一下就體現(xiàn)出來了,。 因?yàn)樵趂or中,,循環(huán)的初始、結(jié)束條件,、循環(huán)的推進(jìn),,都在一起,一眼看上去就知道這是一個(gè)什么樣的循環(huán),。剛出學(xué)校的程序一般對(duì)于鏈接喜歡這樣來: p = pHead; while ( p ){ ... ... p = p->next; } 當(dāng)while的語句塊變大后,,你的程序?qū)⒑茈y讀,用for就好得多: for ( p=pHead; p; p=p->next ){ .. } 一眼就知道這個(gè)循環(huán)的開始條件,,結(jié)束條件,,和循環(huán)的推進(jìn)。大約就能明白這個(gè)循環(huán)要做個(gè)什么事?而且,,程序維護(hù)進(jìn)來很容易,,不必像while一樣,在一個(gè)編輯器中上上下下的搗騰,。 30,、請(qǐng)sizeof類型而不是變量 許多程序員在使用sizeof中,喜歡sizeof變量名,,例如: int score[100]; char filename[20]; struct UserInfo usr[100]; 在sizeof這三個(gè)的變量名時(shí),,都會(huì)返回正確的結(jié)果,于是許多程序員就開始sizeof變量名,。這個(gè)習(xí)慣很雖然沒有什么不好,,但我還是建議sizeof類型。 我看到過這個(gè)的程序: pScore = (int*) malloc( SUBJECT_CNT ); memset( pScore, 0, sizeof(pScore) ); ... 此時(shí),,sizeof(pScore)返回的就是4(指針的長(zhǎng)度),,不會(huì)是整個(gè)數(shù)組,于是,,memset就不能對(duì)這塊內(nèi)存進(jìn)行初始化,。為了程序的易讀和易維護(hù),我強(qiáng)烈建議使用類型而不是變量,,如: 對(duì)于score: sizeof(int) * 100 對(duì)于filename: sizeof(char) * 20 對(duì)于usr: sizeof(struct UserInfo) * 100 這樣的代碼是不是很易讀,?一眼看上去就知道什么意思了。 另外一點(diǎn),,sizeof一般用于分配內(nèi)存,,這個(gè)特性特別在多維數(shù)組時(shí),就能體現(xiàn)出其優(yōu)點(diǎn)了,。如,,給一個(gè)字符串?dāng)?shù)組分配內(nèi)存, char* *p; p = (char**)calloc( 20*100, sizeof(char) ); p = (char**) calloc ( 20, sizeof(char*) ); for ( i=0; i<20; i++){ p = (char*) calloc ( 100, sizeof(char) ); } (注:上述語句被注釋掉的是原來的,,是錯(cuò)誤的,,由dasherest朋友指正,謝謝) 為了代碼的易讀,,省去了一些判斷,,請(qǐng)注意這兩種分配的方法,有本質(zhì)上的差別,。 31,、不要忽略Warning 對(duì)于一些編譯時(shí)的警告信息,請(qǐng)不要忽視它們,。雖然,,這些Warning不會(huì)妨礙目標(biāo)代碼的生成,,但這并不意味著你的程序就是好的。必竟,,并不是編譯成功的程序才是正確的,,編譯成功只是萬里長(zhǎng)征的第一步,后面還有大風(fēng)大浪在等著你,。從編譯程序開始,不但要改正每個(gè)error,,還要修正每個(gè)warning,。這是一個(gè)有修養(yǎng)的程序員該做的事。 一般來說,,一面的一些警告信息是常見的: 1)聲明了未使用的變量,。(雖然編譯器不會(huì)編譯這種變量,但還是把它從源程序中注釋或是刪除吧) 2)使用了隱晦聲明的函數(shù),。(也許此函數(shù)在別的C文件中,,編譯時(shí)會(huì)出現(xiàn)這種警告,你應(yīng)該這之前使用extern關(guān)鍵字聲明這個(gè)函數(shù)) 3)沒有轉(zhuǎn)換一個(gè)指針,。(例如malloc返回的指針是void的,,你沒有把之轉(zhuǎn)成你實(shí)際類型而報(bào)警,還是手動(dòng)的在之前明顯的轉(zhuǎn)換一下吧) 4)類型向下轉(zhuǎn)換,。(例如:float f = 2.0; 這種語句是會(huì)報(bào)警告的,,編譯會(huì)告訴你正試圖把一個(gè)double轉(zhuǎn)成float,你正在閹割一個(gè)變 量,,你真的要這樣做嗎,?還是在2.0后面加個(gè)f吧,不然,,2.0就是一個(gè)double,,而不是float了) 不管怎么說,編譯器的Warning不要小視,,最好不要忽略,,一個(gè)程序都做得出來,何況幾個(gè)小小的Warning呢,? 32,、書寫Debug版和Release版的程序 程序在開發(fā)過程中必然有許多程序員加的調(diào)試信息。我見過許多項(xiàng)目組,,當(dāng)程序開發(fā)結(jié)束時(shí),,發(fā)動(dòng)群眾刪除程序中的調(diào)試信息,何必呢,?為什么不像VC++那樣建立兩個(gè)版本的目標(biāo)代碼,?一個(gè)是debug版本的,,一個(gè)是Release版的。那些調(diào)試信息是那么的寶貴,,在日后的維護(hù)過程中也是很寶貴的東西,,怎么能說刪除就刪除呢? 利用預(yù)編譯技術(shù)吧,,如下所示聲明調(diào)試函數(shù): #ifdef DEBUG void TRACE(char* fmt, ...) { ...... } #else #define TRACE(char* fmt, ...) #endif 于是,,讓所有的程序都用TRACE輸出調(diào)試信息,只需要在在編譯時(shí)加上一個(gè)參數(shù)“-DDEBUG”,,如: cc -DDEBUG -o target target.c 于是,,預(yù)編譯器發(fā)現(xiàn)DEBUG變量被定義了,就會(huì)使用TRACE函數(shù),。而如果要發(fā)布給用戶了,,那么只需要把取消“-DDEBUG”的參數(shù),于是所有用到TRACE宏,,這個(gè)宏什么都沒有,,所以源程序中的所有TRACE語言全部被替換成了空。一舉兩得,,一箭雙雕,,何樂而不為呢? 順便提一下,,兩個(gè)很有用的系統(tǒng)宏,,一個(gè)是“__FILE__”,一個(gè)是“__LINE__”,,分別表示,,所在的源文件和行號(hào),當(dāng)你調(diào)試信息或是輸出錯(cuò)誤時(shí),,可以使用這兩個(gè)宏,,讓你一眼就能看出你的錯(cuò)誤,出現(xiàn)在哪個(gè)文件的第幾行中,。這對(duì)于用C/C++做的大工程非常的管用,。 綜上所述32條,都是為了三大目的-- 1,、程序代碼的易讀性,。 2、程序代碼的可維護(hù)性,, 3,、程序代碼的穩(wěn)定可靠性。 有修養(yǎng)的程序員,,就應(yīng)該要學(xué)會(huì)寫出這樣的代碼,!這是任何一個(gè)想做編程高手所必需面對(duì)的細(xì)小的問題,,編程高手不僅技術(shù)要強(qiáng),基礎(chǔ)要好,,而且最重要的是要有“修養(yǎng)”,! 好的軟件產(chǎn)品絕不僅僅是技術(shù),而更多的是整個(gè)軟件的易維護(hù)和可靠性,。 軟件的維護(hù)有大量的工作量花在代碼的維護(hù)上,,軟件的Upgrade,也有大量的工作花在代碼的組織上,,所以好的代碼,,清淅的,易讀的代碼,,將給大大減少軟件的維護(hù)和升級(jí)成本。 |
|