編程修養(yǎng)(一)
什么是好的程序員,?是不是懂得很多技術(shù)細(xì)節(jié),?還是懂底層編程,?還是編程速度比較快?我覺(jué)得都不是,。對(duì)于一些技術(shù)細(xì)節(jié)來(lái)說(shuō)和底層的技術(shù),,只要看幫助,查資料就能找到,,對(duì)于速度快,,只要編得多也就熟能生巧了。
我認(rèn)為好的程序員應(yīng)該有以下幾方面的素質(zhì):
1,、有專研精神,,勤學(xué)善問(wèn)、舉一反三。
2,、積極向上的態(tài)度,,有創(chuàng)造性思維。
3,、與人積極交流溝通的能力,,有團(tuán)隊(duì)精神。
4,、謙虛謹(jǐn)慎,,戒驕戒燥。
5,、寫出的代碼質(zhì)量高,。包括:代碼的穩(wěn)定、易讀,、規(guī)范,、易維護(hù)、專業(yè),。
這些都是程序員的修養(yǎng),,這里我想談?wù)?/SPAN>“編程修養(yǎng)”,也就是上述中的第5點(diǎn),。我覺(jué)得,,如果我要了解一個(gè)作者,我會(huì)看他所寫的小說(shuō),,如果我要了解一個(gè)畫家,,我會(huì)看他所畫的圖畫,如果我要了解一個(gè)工人,,我會(huì)看他所做出來(lái)的產(chǎn)品,,同樣,如果我要了解一個(gè)程序員,,我想首先我最想看的就是他的程序代碼,,程序代碼可以看出一個(gè)程序員的素質(zhì)和修養(yǎng),程序就像一個(gè)作品,,有素質(zhì)有修養(yǎng)的程序員的作品必然是一圖精美的圖畫,,一首美妙的歌曲,一本賞心悅目的小說(shuō),。
我看過(guò)許多程序,,沒(méi)有注釋,沒(méi)有縮進(jìn),,胡亂命名的變量名,,等等,等等,我把這種人統(tǒng)稱為沒(méi)有修養(yǎng)的程序,,這種程序員,,是在做創(chuàng)造性的工作嗎?不,,完全就是在搞破壞,,他們與其說(shuō)是在編程,還不如說(shuō)是在對(duì)源程序進(jìn)行“加密”,,這種程序員,,見一個(gè)就應(yīng)該開除一個(gè),因?yàn)樗幍某绦蛩鶆?chuàng)造的價(jià)值,,遠(yuǎn)遠(yuǎn)小于需要在上面進(jìn)行維護(hù)的價(jià)值,。
程序員應(yīng)該有程序員的修養(yǎng),那怕再累,,再?zèng)]時(shí)間,,也要對(duì)自己的程序負(fù)責(zé)。我寧可要那種動(dòng)作慢,,技術(shù)一般,,但有良好的寫程序風(fēng)格的程序員,也不要那種技術(shù)強(qiáng),、動(dòng)作快的“搞破壞”的程序員,。有句話叫“字如其人”,我想從程序上也能看出一個(gè)程序員的優(yōu)劣,。因?yàn)?,程序是程序員的作品,作品的好壞直截關(guān)系到程序員的聲譽(yù)和素質(zhì),。而“修養(yǎng)”好的程序員一定能做出好的程序和軟件,。
有個(gè)成語(yǔ)叫“獨(dú)具匠心”,意思是做什么都要做得很專業(yè),,很用心,,如果你要做一個(gè)“匠”,也就是造詣高深的人,,那么,,從一件很簡(jiǎn)單的作品上就能看出你有沒(méi)有“匠”的特性,我覺(jué)得做一個(gè)程序員不難,,但要做一個(gè)“程序匠”就不簡(jiǎn)單了。編程序很簡(jiǎn)單,,但編出有質(zhì)量的程序就難了,。
我在這里不討論過(guò)深的技術(shù),我只想在一些容易讓人忽略的東西上說(shuō)一說(shuō),雖然這些東西可能很細(xì)微,,但如果你不注意這些細(xì)微之處的話,,那么他將會(huì)極大的影響你的整個(gè)軟件質(zhì)量,以及整個(gè)軟件程的實(shí)施,,所謂“千里之堤,,毀于蟻穴”。
“細(xì)微之處見真功”,,真正能體現(xiàn)一個(gè)程序的功底恰恰在這些細(xì)微之處,。
這就是程序員的——編程修養(yǎng)。我總結(jié)了在用C/C++語(yǔ)言(主要是C語(yǔ)言)進(jìn)行程序?qū)懽魃系娜€(gè)“修養(yǎng)”,,通過(guò)這些,,你可以寫出質(zhì)量高的程序,同時(shí)也會(huì)讓看你程序的人漬漬稱道,,那些看過(guò)你程序的人一定會(huì)說(shuō):“這個(gè)人的編程修養(yǎng)不錯(cuò)”。
————————————————————————
01、版權(quán)和版本
02,、縮進(jìn),、空格、換行,、空行,、對(duì)齊
03、程序注釋
04,、函數(shù)的[in][out]參數(shù)
05,、對(duì)系統(tǒng)調(diào)用的返回進(jìn)行判斷
06、if 語(yǔ)句對(duì)出錯(cuò)的處理
07,、頭文件中的#ifndef
08,、在堆上分配內(nèi)存
09、變量的初始化
10,、h和c文件的使用
11,、出錯(cuò)信息的處理
12、常用函數(shù)和循環(huán)語(yǔ)句中的被計(jì)算量
13,、函數(shù)名和變量名的命名
14,、函數(shù)的傳值和傳指針
15、修改別人程序的修養(yǎng)
16,、把相同或近乎相同的代碼形成函數(shù)和宏
17,、表達(dá)式中的括號(hào)
18、函數(shù)參數(shù)中的const
19,、函數(shù)的參數(shù)個(gè)數(shù)
20,、函數(shù)的返回類型,,不要省略
21、goto語(yǔ)句的使用
22,、宏的使用
23,、static的使用
24、函數(shù)中的代碼尺寸
25,、typedef的使用
26,、為常量聲明宏
27、不要為宏定義加分號(hào)
28,、||和&&的語(yǔ)句執(zhí)行順序
29,、盡量用for而不是while做循環(huán)
30、請(qǐng)sizeof類型而不是變量
31,、不要忽略Warning
32,、書寫Debug版和Release版的程序
1、版權(quán)和版本
———————
好的程序員會(huì)給自己的每個(gè)函數(shù),,每個(gè)文件,,都注上版權(quán)和版本。
對(duì)于C/C++的文件,,文件頭應(yīng)該有類似這樣的注釋:
/************************************************************************
*
* 文件名:network.c
*
* 文件描述:網(wǎng)絡(luò)通訊函數(shù)集
*
* 創(chuàng)建人: Hao Chen, 2003年2月3日
*
* 版本號(hào):1.0
*
* 修改記錄:
*
************************************************************************/
而對(duì)于函數(shù)來(lái)說(shuō),,應(yīng)該也有類似于這樣的注釋:
/*================================================================
*
* 函 數(shù) 名:XXX
*
* 參 數(shù):
*
* type name [IN] : descripts
*
* 功能描述:
*
* ..............
*
* 返 回 值:成功TRUE,失敗FALSE
*
* 拋出異常:
*
* 作 者:ChenHao 2003/4/2
*
================================================================*/
這樣的描述可以讓人對(duì)一個(gè)函數(shù),,一個(gè)文件有一個(gè)總體的認(rèn)識(shí),,對(duì)代碼的易讀性和易維護(hù)性有很大的好處。這是好的作品產(chǎn)生的開始,。
2,、縮進(jìn)、空格,、換行,、空行、對(duì)齊
————————————————
i) 縮進(jìn)應(yīng)該是每個(gè)程序都會(huì)做的,,只要學(xué)程序過(guò)程序就應(yīng)該知道這個(gè),,但是我仍然看過(guò)不縮進(jìn)的程序,或是亂縮進(jìn)的程序,,如果你的公司還有寫程序不縮進(jìn)的程序員,,請(qǐng)毫不猶豫的開除他吧,并以破壞源碼罪起訴他,,還要他賠償讀過(guò)他程序的人的精神損失費(fèi),。縮進(jìn),,這是不成文規(guī)矩,,我再重提一下吧,,一個(gè)縮進(jìn)一般是一個(gè)TAB鍵或是4個(gè)空格,。(最好用TAB鍵)
ii) 空格,。空格能給程序代來(lái)什么損失嗎,?沒(méi)有,,有效的利用空格可以讓你的程序讀進(jìn)來(lái)更加賞心悅目。而不一堆表達(dá)式擠在一起,??纯聪旅娴拇a:
ha=(ha*128+*key++)%tabPtr->size;
ha = ( ha * 128 + *key++ ) % tabPtr->size;
有空格和沒(méi)有空格的感覺(jué)不一樣吧。一般來(lái)說(shuō),,語(yǔ)句中要在各個(gè)操作符間加空格,,函數(shù)調(diào)用時(shí),要以各個(gè)參數(shù)間加空格,。如下面這種加空格的和不加的:
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){
}
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){
}
iii) 換行,。不要把語(yǔ)句都寫在一行上,這樣很不好,。如:
for(i=0;i‘9‘)&&(a[i]<‘a(chǎn)‘||a[i]>‘z‘)) break;
我拷,,這種即無(wú)空格,又無(wú)換行的程序在寫什么???加上空格和換行吧。
for ( i=0; i if ( ( a[i] < ‘0‘ || a[i] > ‘9‘ ) &&
( a[i] < ‘a(chǎn)‘ || a[i] > ‘z‘ ) ) {
break;
}
}
好多了吧,?有時(shí)候,,函數(shù)參數(shù)多的時(shí)候,最好也換行,,如:
CreateProcess(
NULL,
cmdbuf,
NULL,
NULL,
bInhH,
dwCrtFlags,
envbuf,
NULL,
&siStartInfo,
&prInfo
);
條件語(yǔ)句也應(yīng)該在必要時(shí)換行:
if ( ch >= ‘0‘ || ch <= ‘9‘ ||
ch >= ‘a(chǎn)‘ || ch <= ‘z‘ ||
ch >= ‘A‘ || ch <= ‘Z‘ )
iv) 空行,。不要不加空行,空行可以區(qū)分不同的程序塊,,程序塊間,,最好加上空行。如:
HANDLE hProcess;
PROCESS_T procInfo;
/* open the process handle */
if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)
{
return LSE_MISC_SYS;
}
memset(&procInfo, 0, sizeof(procInfo));
procInfo.idProc = pid;
procInfo.hdProc = hProcess;
procInfo.misc |= MSCAVA_PROC;
return(0);
v) 對(duì)齊,。用TAB鍵對(duì)齊你的一些變量的聲明或注釋,,一樣會(huì)讓你的程序好看一些。如:
typedef struct _pt_man_t_ {
int numProc; /* Number of processes */
int maxProc; /* Max Number of processes */
int numEvnt; /* Number of events */
int maxEvnt; /* Max Number of events */
HANDLE* pHndEvnt; /* Array of events */
DWORD timeout; /* Time out interval */
HANDLE hPipe; /* Namedpipe */
TCHAR usr[MAXUSR];/* User name of the process */
int numMsg; /* Number of Message */
int Msg[MAXMSG];/* Space for intro process communicate */
} PT_MAN_T;
怎么樣,?感覺(jué)不錯(cuò)吧,。
這里主要講述了如果寫出讓人賞心悅目的代碼,好看的代碼會(huì)讓人的心情愉快,,讀起代碼也就不累,,工整,、整潔的程序代碼,通常更讓人歡迎,,也更讓人稱道?,F(xiàn)在的硬盤空間這么大,不要讓你的代碼擠在一起,,這樣它們會(huì)抱怨你虐待它們的,。好了,用“縮進(jìn),、空格,、換行、空行,、對(duì)齊”裝飾你的代碼吧,,讓他們從沒(méi)有秩序的土匪中變成一排排整齊有秩序的正規(guī)部隊(duì)吧。
3,、程序注釋
——————
養(yǎng)成寫程序注釋的習(xí)慣,,這是每個(gè)程序員所必須要做的工作。我看過(guò)那種幾千行,,卻居然沒(méi)有一行注釋的程序,。這就如同在公路上駕車卻沒(méi)有路標(biāo)一樣。用不了多久,,連自己都不知道自己的意圖了,,還要花上幾倍的時(shí)間才看明白,這種浪費(fèi)別人和自己的時(shí)間的人,,是最為可恥的人,。
是的,你也許會(huì)說(shuō),,你會(huì)寫注釋,,真的嗎?注釋的書寫也能看出一個(gè)程序員的功底,。一般來(lái)說(shuō)你需要至少寫這些地方的注釋:文件的注釋,、函數(shù)的注釋、變量的注釋,、算法的注釋,、功能塊的程序注釋。主要就是記錄你這段程序是干什么的,?你的意圖是什么,?你這個(gè)變量是用來(lái)做什么的?等等,。
不要以為注釋好寫,,有一些算法是很難說(shuō)或?qū)懗鰜?lái)的,,只能意會(huì),我承認(rèn)有這種情況的時(shí)候,,但你也要寫出來(lái),,正好可以訓(xùn)練一下自己的表達(dá)能力。而表達(dá)能力正是那種悶頭搞技術(shù)的技術(shù)人員最缺的,,你有再高的技術(shù),,如果你表達(dá)能力不行,,你的技術(shù)將不能得到充分的發(fā)揮,。因?yàn)椋@是一個(gè)團(tuán)隊(duì)的時(shí)代,。
好了,,說(shuō)幾個(gè)注釋的技術(shù)細(xì)節(jié):
i) 對(duì)于行注釋(“//”)比塊注釋(“/* */”)要好的說(shuō)法,我并不是很同意,。因?yàn)橐恍├习姹镜?/SPAN>C編譯器并不支持行注釋,,所以為了你的程序的移植性,請(qǐng)你還是盡量使用塊注釋,。
ii) 你也許會(huì)為塊注釋的不能嵌套而不爽,,那么你可以用預(yù)編譯來(lái)完成這個(gè)功能。使用“#if 0”和“#endif”括起來(lái)的代碼,,將不被編譯,,而且還可以嵌套。
4,、函數(shù)的[in][out]參數(shù)
———————————
我經(jīng)??吹竭@樣的程序:
FuncName(char* str)
{
int len = strlen(str);
.....
}
char*
GetUserName(struct user* pUser)
{
return pUser->name;
}
不!請(qǐng)不要這樣做,。
你應(yīng)該先判斷一下傳進(jìn)來(lái)的那個(gè)指針是不是為空,。如果傳進(jìn)來(lái)的指針為空的話,那么,,你的一個(gè)大的系統(tǒng)就會(huì)因?yàn)檫@一個(gè)小的函數(shù)而崩潰,。一種更好的技術(shù)是使用斷言(assert),這里我就不多說(shuō)這些技術(shù)細(xì)節(jié)了,。當(dāng)然,,如果是在C++中,引用要比指針好得多,,但你也需要對(duì)各個(gè)參數(shù)進(jìn)行檢查,。
寫有參數(shù)的函數(shù)時(shí),首要工作,,就是要對(duì)傳進(jìn)來(lái)的所有參數(shù)進(jìn)行合法性檢查,。而對(duì)于傳出的參數(shù)也應(yīng)該進(jìn)行檢查,,這個(gè)動(dòng)作當(dāng)然應(yīng)該在函數(shù)的外部,也就是說(shuō),,調(diào)用完一個(gè)函數(shù)后,,應(yīng)該對(duì)其傳出的值進(jìn)行檢查。
當(dāng)然,,檢查會(huì)浪費(fèi)一點(diǎn)時(shí)間,,但為了整個(gè)系統(tǒng)不至于出現(xiàn)“非法操作”或是“Core Dump”的系統(tǒng)級(jí)的錯(cuò)誤,多花這點(diǎn)時(shí)間還是很值得的,。
5,、對(duì)系統(tǒng)調(diào)用的返回進(jìn)行判斷
——————————————
繼續(xù)上一條,對(duì)于一些系統(tǒng)調(diào)用,,比如打開文件,,我經(jīng)常看到,,許多程序員對(duì)fopen返回的指針不做任何判斷,,就直接使用了。然后發(fā)現(xiàn)文件的內(nèi)容怎么也讀出不,,或是怎么也寫不進(jìn)去,。還是判斷一下吧:
fp = fopen("log.txt", "a");
if ( fp == NULL ){
printf("Error: open file error\n");
return FALSE;
}
其它還有許多啦,比如:socket返回的socket號(hào),,malloc返回的內(nèi)存,。請(qǐng)對(duì)這些系統(tǒng)調(diào)用返回的東西進(jìn)行判斷。
6,、if 語(yǔ)句對(duì)出錯(cuò)的處理
———————————
我看見你說(shuō)了,,這有什么好說(shuō)的。還是先看一段程序代碼吧,。
if ( ch >= ‘0‘ && ch <= ‘9‘ ){
/* 正常處理代碼 */
}else{
/* 輸出錯(cuò)誤信息 */
printf("error ......\n");
return ( FALSE );
}
這種結(jié)構(gòu)很不好,,特別是如果“正常處理代碼”很長(zhǎng)時(shí),對(duì)于這種情況,,最好不要用else,。先判斷錯(cuò)誤,如:
if ( ch < ‘0‘ || ch > ‘9‘ ){
/* 輸出錯(cuò)誤信息 */
printf("error ......\n");
return ( FALSE );
}
/* 正常處理代碼 */
......
這樣的結(jié)構(gòu),,不是很清楚嗎,?突出了錯(cuò)誤的條件,讓別人在使用你的函數(shù)的時(shí)候,,第一眼就能看到不合法的條件,,于是就會(huì)更下意識(shí)的避免。
7、頭文件中的#ifndef
——————————
千萬(wàn)不要忽略了頭件的中的#ifndef,,這是一個(gè)很關(guān)鍵的東西,。比如你有兩個(gè)C文件,這兩個(gè)C文件都include了同一個(gè)頭文件,。而編譯時(shí),,這兩個(gè)C文件要一同編譯成一個(gè)可運(yùn)行文件,于是問(wèn)題來(lái)了,,大量的聲明沖突,。
還是把頭文件的內(nèi)容都放在#ifndef和#endif中吧。不管你的頭文件會(huì)不會(huì)被多個(gè)文件引用,,你都要加上這個(gè),。一般格式是這樣的:
#ifndef <標(biāo)識(shí)>
#define <標(biāo)識(shí)>
......
......
#endif
<標(biāo)識(shí)>在理論上來(lái)說(shuō)可以是自由命名的,但每個(gè)頭文件的這個(gè)“標(biāo)識(shí)”都應(yīng)該是唯一的,。標(biāo)識(shí)的命名規(guī)則一般是頭文件名全大寫,,前后加下劃線,并把文件名中的“.”也變成下劃線,,如:stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif
(BTW:預(yù)編譯有多很有用的功能。你會(huì)用預(yù)編譯嗎,?)
8,、在堆上分配內(nèi)存
—————————
可能許多人對(duì)內(nèi)存分配上的“棧 stack”和“堆 heap”還不是很明白。包括一些科班出身的人也不明白這兩個(gè)概念,。我不想過(guò)多的說(shuō)這兩個(gè)東西,。簡(jiǎn)單的來(lái)講,stack上分配的內(nèi)存系統(tǒng)自動(dòng)釋放,,heap上分配的內(nèi)存,,系統(tǒng)不釋放,哪怕程序退出,,那一塊內(nèi)存還是在那里,。stack一般是靜態(tài)分配內(nèi)存,heap上一般是動(dòng)態(tài)分配內(nèi)存,。
由malloc系統(tǒng)函數(shù)分配的內(nèi)存就是從堆上分配內(nèi)存,。從堆上分配的內(nèi)存一定要自己釋放。用free釋放,,不然就是術(shù)語(yǔ)——“內(nèi)存泄露”(或是“內(nèi)存漏洞”)—— Memory Leak,。于是,系統(tǒng)的可分配內(nèi)存會(huì)隨malloc越來(lái)越少,,直到系統(tǒng)崩潰,。還是來(lái)看看“棧內(nèi)存”和“堆內(nèi)存”的差別吧。
棧內(nèi)存分配
—————
char*
AllocStrFromStack()
{
char pstr[100];
return pstr;
}
堆內(nèi)存分配
—————
char*
AllocStrFromHeap(int len)
{
char *pstr;
if ( len <= 0 ) return NULL;
return ( char* ) malloc( len );
}
對(duì)于第一個(gè)函數(shù),那塊pstr的內(nèi)存在函數(shù)返回時(shí)就被系統(tǒng)釋放了,。于是所返回的char*什么也沒(méi)有,。而對(duì)于第二個(gè)函數(shù),是從堆上分配內(nèi)存,,所以哪怕是程序退出時(shí),,也不釋放,所以第二個(gè)函數(shù)的返回的內(nèi)存沒(méi)有問(wèn)題,,可以被使用,。但一定要調(diào)用free釋放,不然就是Memory Leak,!
在堆上分配內(nèi)存很容易造成內(nèi)存泄漏,,這是C/C++的最大的“克星”,如果你的程序要穩(wěn)定,,那么就不要出現(xiàn)Memory Leak,。所以,我還是要在這里千叮嚀萬(wàn)囑付,,在使用malloc系統(tǒng)函數(shù)(包括calloc,,realloc)時(shí)千萬(wàn)要小心。
記得有一個(gè)UNIX上的服務(wù)應(yīng)用程序,,大約有幾百的C文件編譯而成,,運(yùn)行測(cè)試良好,等使用時(shí),,每隔三個(gè)月系統(tǒng)就是down一次,,搞得許多人焦頭爛額,查不出問(wèn)題所在,。只好,,每隔兩個(gè)月人工手動(dòng)重啟系統(tǒng)一次。出現(xiàn)這種問(wèn)題就是Memery Leak在做怪了,,在C/C++中這種問(wèn)題總是會(huì)發(fā)生,,所以你一定要小心。一個(gè)Rational的檢測(cè)工作——Purify,,可以幫你測(cè)試你的程序有沒(méi)有內(nèi)存泄漏,。
我保證,做過(guò)許多C/C++的工程的程序員,,都會(huì)對(duì)malloc或是new有些感冒,。當(dāng)你什么時(shí)候在使用malloc和new時(shí),有一種輕度的緊張和惶恐的感覺(jué)時(shí),,你就具備了這方面的修養(yǎng)了,。
對(duì)于malloc和free的操作有以下規(guī)則:
1) 配對(duì)使用,,有一個(gè)malloc,就應(yīng)該有一個(gè)free,。(C++中對(duì)應(yīng)為new和delete)
2) 盡量在同一層上使用,,不要像上面那種,malloc在函數(shù)中,,而free在函數(shù)外,。最好在同一調(diào)用層上使用這兩個(gè)函數(shù)。
3) malloc分配的內(nèi)存一定要初始化,。free后的指針一定要設(shè)置為NULL,。
注:雖然現(xiàn)在的操作系統(tǒng)(如:UNIX和Win2k/NT)都有進(jìn)程內(nèi)存跟蹤機(jī)制,也就是如果你有沒(méi)有釋放的內(nèi)存,,操作系統(tǒng)會(huì)幫你釋放,。但操作系統(tǒng)依然不會(huì)釋放你程序中所有產(chǎn)生了Memory Leak的內(nèi)存,所以,,最好還是你自己來(lái)做這個(gè)工作,。(有的時(shí)候不知不覺(jué)就出現(xiàn)Memory Leak了,而且在幾百萬(wàn)行的代碼中找無(wú)異于海底撈針,,Rational有一個(gè)工具叫Purify,,可能很好的幫你檢查程序中的Memory Leak)
9、變量的初始化
————————
接上一條,,變量一定要被初始化再使用,。C/C++編譯器在這個(gè)方面不會(huì)像JAVA一樣幫你初始化,這一切都需要你自己來(lái),,如果你使用了沒(méi)有初始化的變量,結(jié)果未知,。好的程序員從來(lái)都會(huì)在使用變量前初始化變量的,。如:
1) 對(duì)malloc分配的內(nèi)存進(jìn)行memset清零操作。(可以使用calloc分配一塊全零的內(nèi)存)
2) 對(duì)一些棧上分配的struct或數(shù)組進(jìn)行初始化,。(最好也是清零)
不過(guò)話又說(shuō)回來(lái)了,,初始化也會(huì)造成系統(tǒng)運(yùn)行時(shí)間有一定的開銷,所以,,也不要對(duì)所有的變量做初始化,,這個(gè)也沒(méi)有意義。好的程序員知道哪些變量需要初始化,,哪些則不需要,。如:以下這種情況,則不需要,。
char *pstr; /* 一個(gè)字符串 */
pstr = ( char* ) malloc( 50 );
if ( pstr == NULL ) exit(0);
strcpy( pstr, "Hello Wrold" );
但如果是下面一種情況,,最好進(jìn)行內(nèi)存初始化。(指針是一個(gè)危險(xiǎn)的東西,一定要初始化)
char **pstr; /* 一個(gè)字符串?dāng)?shù)組 */
pstr = ( char** ) malloc( 50 );
if ( pstr == NULL ) exit(0);
/* 讓數(shù)組中的指針都指向NULL */
memset( pstr, 0, 50*sizeof(char*) );
而對(duì)于全局變量,,和靜態(tài)變量,,一定要聲明時(shí)就初始化。因?yàn)槟悴恢浪谝淮螘?huì)在哪里被使用,。所以使用前初始這些變量是比較不現(xiàn)實(shí)的,,一定要在聲明時(shí)就初始化它們。如:
Links *plnk = NULL; /* 對(duì)于全局變量plnk初始化為NULL */
10,、h和c文件的使用
—————————
H文件和C文件怎么用呢,?一般來(lái)說(shuō),H文件中是declare(聲明),,C文件中是define(定義),。因?yàn)?/SPAN>C文件要編譯成庫(kù)文件(Windows下是.obj/.lib,UNIX下是.o/.a),,如果別人要使用你的函數(shù),,那么就要引用你的H文件,所以,,H文件中一般是變量,、宏定義、枚舉,、結(jié)構(gòu)和函數(shù)接口的聲明,,就像一個(gè)接口說(shuō)明文件一樣。而C文件則是實(shí)現(xiàn)細(xì)節(jié),。
H文件和C文件最大的用處就是聲明和實(shí)現(xiàn)分開,。這個(gè)特性應(yīng)該是公認(rèn)的了,但我仍然看到有些人喜歡把函數(shù)寫在H文件中,,這種習(xí)慣很不好,。(如果是C++話,對(duì)于其模板函數(shù),,在VC中只有把實(shí)現(xiàn)和聲明都寫在一個(gè)文件中,,因?yàn)?/SPAN>VC不支持export關(guān)鍵字)。而且,,如果在H文件中寫上函數(shù)的實(shí)現(xiàn),,你還得在makefile中把頭文件的依賴關(guān)系也加上去,這個(gè)就會(huì)讓你的makefile很不規(guī)范,。
最后,,有一個(gè)最需要注意的地方就是:帶初始化的全局變量不要放在H文件中!
例如有一個(gè)處理錯(cuò)誤信息的結(jié)構(gòu):
char* errmsg[] = {
/* 0 */ "No error",
/* 1 */ "Open file error",
/* 2 */ "Failed in sending/receiving a message",
/* 3 */ "Bad arguments",
/* 4 */ "Memeroy is not enough",
/* 5 */ "Service is down; try later",
/* 6 */ "Unknow information",
/* 7 */ "A socket operation has failed",
/* 8 */ "Permission denied",
/* 9 */ "Bad configuration file format",
/* 10 */ "Communication time out",
......
......
};
請(qǐng)不要把這個(gè)東西放在頭文件中,,因?yàn)槿绻愕倪@個(gè)頭文件被5個(gè)函數(shù)庫(kù)(.lib或是.a)所用到,,于是他就被鏈接在這5個(gè).lib或.a中,,而如果你的一個(gè)程序用到了這5個(gè)函數(shù)庫(kù)中的函數(shù),并且這些函數(shù)都用到了這個(gè)出錯(cuò)信息數(shù)組,。那么這份信息將有5個(gè)副本存在于你的執(zhí)行文件中,。如果你的這個(gè)errmsg很大的話,而且你用到的函數(shù)庫(kù)更多的話,,你的執(zhí)行文件也會(huì)變得很大,。
正確的寫法應(yīng)該把它寫到C文件中,然后在各個(gè)需要用到errmsg的C文件頭上加上 extern char* errmsg[]; 的外部聲明,,讓編譯器在鏈接時(shí)才去管他,,這樣一來(lái),就只會(huì)有一個(gè)errmsg存在于執(zhí)行文件中,,而且,,這樣做很利于封裝。
我曾遇到過(guò)的最瘋狂的事,,就是在我的目標(biāo)文件中,,這個(gè)errmsg一共有112個(gè)副本,執(zhí)行文件有8M左右,。當(dāng)我把errmsg放到C文件中,,并為一千多個(gè)C文件加上了extern的聲明后,所有的函數(shù)庫(kù)文件尺寸都下降了20%左右,,而我的執(zhí)行文件只有5M了,。一下子少了3M啊。
[ 備注 ]
—————
有朋友對(duì)我說(shuō),,這個(gè)只是一個(gè)特例,,因?yàn)椋绻?/SPAN>errmsg在執(zhí)行文件中存在多個(gè)副本時(shí),,可以加快程序運(yùn)行速度,,理由是errmsg的多個(gè)復(fù)本會(huì)讓系統(tǒng)的內(nèi)存換頁(yè)降低,達(dá)到效率提升,。像我們這里所說(shuō)的errmsg只有一份,當(dāng)某函數(shù)要用errmsg時(shí),,如果內(nèi)存隔得比較遠(yuǎn),,會(huì)產(chǎn)生換頁(yè),反而效率不高,。
這個(gè)說(shuō)法不無(wú)道理,,但是一般而言,對(duì)于一個(gè)比較大的系統(tǒng),,errmsg是比較大的,,所以產(chǎn)生副本導(dǎo)致執(zhí)行文件尺寸變大,,不僅增加了系統(tǒng)裝載時(shí)間,也會(huì)讓一個(gè)程序在內(nèi)存中占更多的頁(yè)面,。而對(duì)于errmsg這樣數(shù)據(jù),,一般來(lái)說(shuō),在系統(tǒng)運(yùn)行時(shí)不會(huì)經(jīng)常用到,,所以還是產(chǎn)生的內(nèi)存換頁(yè)也就不算頻繁,。權(quán)衡之下,還是只有一份errmsg的效率高,。即便是像logmsg這樣頻繁使用的的數(shù)據(jù),,操作系統(tǒng)的內(nèi)存調(diào)度算法會(huì)讓這樣的頻繁使用的頁(yè)面常駐于內(nèi)存,所以也就不會(huì)出現(xiàn)內(nèi)存換頁(yè)問(wèn)題了,。
11,、出錯(cuò)信息的處理
—————————
你會(huì)處理出錯(cuò)信息嗎?哦,,它并不是簡(jiǎn)單的輸出,。看下面的示例:
if ( p == NULL ){
printf ( "ERR: The pointer is NULL\n" );
}
告別學(xué)生時(shí)代的編程吧,。這種編程很不利于維護(hù)和管理,,出錯(cuò)信息或是提示信息,應(yīng)該統(tǒng)一處理,,而不是像上面這樣,,寫成一個(gè)“硬編碼”。第10條對(duì)這方面的處理做了一部分說(shuō)明,。如果要管理錯(cuò)誤信息,,那就要有以下的處理:
/* 聲明出錯(cuò)代碼 */
#define ERR_NO_ERROR 0 /* No error */
#define ERR_OPEN_FILE 1 /* Open file error */
#define ERR_SEND_MESG 2 /* sending a message error */
#define ERR_BAD_ARGS 3 /* Bad arguments */
#define ERR_MEM_NONE 4 /* Memeroy is not enough */
#define ERR_SERV_DOWN 5 /* Service down try later */
#define ERR_UNKNOW_INFO 6 /* Unknow information */
#define ERR_SOCKET_ERR 7 /* Socket operation failed */
#define ERR_PERMISSION 8 /* Permission denied */
#define ERR_BAD_formAT 9 /* Bad configuration file */
#define ERR_TIME_OUT 10 /* Communication time out */
/* 聲明出錯(cuò)信息 */
char* errmsg[] = {
/* 0 */ "No error",
/* 1 */ "Open file error",
/* 2 */ "Failed in sending/receiving a message",
/* 3 */ "Bad arguments",
/* 4 */ "Memeroy is not enough",
/* 5 */ "Service is down; try later",
/* 6 */ "Unknow information",
/* 7 */ "A socket operation has failed",
/* 8 */ "Permission denied",
/* 9 */ "Bad configuration file format",
/* 10 */ "Communication time out",
};
/* 聲明錯(cuò)誤代碼全局變量 */
long errno = 0;
/* 打印出錯(cuò)信息函數(shù) */
void perror( char* info)
{
if ( info ){
printf("%s: %s\n", info, errmsg[errno] );
return;
}
printf("Error: %s\n", 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)然,,物極必反,也沒(méi)有必要把所有的輸出都放到errmsg中,,抽取比較重要的出錯(cuò)信息或是提示信息是其關(guān)鍵,,但即使這樣,,這也包括了大多數(shù)的信息。
12,、常用函數(shù)和循環(huán)語(yǔ)句中的被計(jì)算量
—————————————————
看一下下面這個(gè)例子:
for( i=0; i<1000; i++ ){
GetLocalHostName( hostname );
...
}
GetLocalHostName的意思是取得當(dāng)前計(jì)算機(jī)名,,在循環(huán)體中,它會(huì)被調(diào)用1000次啊,。這是多么的沒(méi)有效率的事啊,。應(yīng)該把這個(gè)函數(shù)拿到循環(huán)體外,這樣只調(diào)用一次,,效率得到了很大的提高,。雖然,我們的編譯器會(huì)進(jìn)行優(yōu)化,,會(huì)把循環(huán)體內(nèi)的不變的東西拿到循環(huán)外面,,但是,你相信所有編譯器會(huì)知道哪些是不變的嗎,?我覺(jué)得編譯器不可靠,。最好還是自己動(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,,這同樣是一種沒(méi)有“修養(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)格保持一致。