Unix C 編程問題精粹
文章目錄 第一章:前言 第二章:約定 第三章:開始任務 第四章:使用lint 第五章:使用make 第六章:優質無錯編程 第七章:調試技術 第八章:其它更好的文檔
第一章:前言
對于C語言,有人認為它已經落伍了.對于這個問題,仁者見仕,智者見智.的確,C++比C有更強大的諸多優勢.但C++是建立在C之上的.這也是Herbert Schildt所著書在全世界暢銷不衰的原因.更何況,要深入學習Linux就必需要有相當的C功底.(這也是我搜集整理本文的根由:-) 現結合個人在編程中的體會,為使新手少走彎路,為老手錦上添花,因此無論你是使用C或C++編程,也無論你是程序設計的初學者還是成熟的專業人員,均會發現,本文將會對你有所收益.當然,我盡力寫得清晰易懂,又不古板. 我愛C.(正如世人愛上帝一樣:-)..
第二章:約定
專業的源程書寫風格. 先看看世界級C大師的源程書寫風格.如 Steve Maguire 就有許多不錯的建議.
[]倡導使用易于理解的'匈牙利式'的命名約定. 所有的字符變量均以ch開始; 如: char ch_****; 所有的字節變量均冠以b; 如: byte b_****; 所有的長字變量均冠以l; 如: long l_****; 所有的指針變量均冠以P; 如: char *p_ch_****; 建議類型派生出的基本名字之后加上一個以大寫字母開頭的'標簽'.如: 分析 char **ppchMydata; 其讓人一眼就能看出它代表一個指向字符指針Mydata的指針. '匈牙利式'命名的最大不足是難念:-(( .但相對于不是總統演講稿的C源程來說,這又算得了什么?想想看以下的數據命名: char a,b,c; long d,e,f; . . . (反正我是不會再看下去了...)
[]倡導規范書寫. 如果你思如泉涌,而不去也不及顧慮書寫格式,那也沒關系.在將其交出去之前,用cb命令格式化你的源程.雖然源程的格式不會影響到你編譯結果的正確性,但切記,能讓其他的程序員能輕松地閱讀它.否則沒人會理你的. 關于cb命令的更多用法,可以用man cb來參考其手冊頁. 當然除了cb之外,還有更多更好的.但cb是你在任何Unix(LINUX)上都找得到的.更何況它并不差.
第三章:開始任務
開始任務之前,先做個深呼吸!
[]其他文檔你準備好了嗎? 你是不是除了C源程之外一無所有了嗎?兵馬未動,糧草先行.你必須先清楚該程序所要完成的功能.在開始寫程序之前,對程序的功能應有規范說明.書寫規范書和確知程序功能的一個方法是先編寫相應的操作手冊.如果你是一人單干,勸你首先寫需求書.切記切記,這對你意味著事半功倍的大好事. 一個實例:我計劃為本行的信貸子功能模塊打一個補丁.我用10周的時間用來寫規劃書,需求書,操作流程,使用說明等等文檔.之后用2周的時間編寫程序,在初步測試(1周)后遞交給各信貸部門測試使用.然后根據反饋的信息再更改相應文檔,并根據文檔修改源程.6個月后發布正式版.
[]一定該遵循ANSI標準嗎? 如果你僅使用ANSI的標準首標文件,恭喜你,你的程序有著全世界范圍內的廣泛支持和兼容.光明無限.但你必須在通用與專用之間做出取舍,對不起,我幫不了你. 我的原則是:核心用ANSI,界面按需而取.這樣在轉換平臺時僅需另編用戶界面而已.實用至上嘛. 附:ANSI 標準C頭文件
是不是很寒酸?
[]再續前緣? 在得到新任務之后并在開始該新任務之前應馬上回想有哪些是曾經擁有的.舊調重彈遠比另起爐灶來的高效與環保.
[]是否該有自已的庫? 我的答案是應該有自已的特色庫,并與ANSI兼容.與3.8不同的是,你僅需在源程序之后附上自已的專用庫就可以了.其次在有了自已的庫后,源碼會很精煉的.不用去羨慕別人了吧.
[]要學會條件編譯.注意你的平臺特性.(高手的標志?) 除非你確定你要寫的程序是在某特定的OS特定的硬件平臺而量身定做.否則應注意數據類型的長度,精度都是不同的,不要想當然.有時甚至是不同的編譯器的差異都要考慮考慮.
.... ....(歡迎您來充實此處空白) ....
好了,在任務中,又有哪些細節呢?
[]我是不是葛郎臺? 不要那么吝嗇.在源程序中加入詳盡的注釋以使自己和他人即使在許多年以后仍能讀明白它是什么樣的程序. 用注釋行分離各個函數.
[]刪除不需要的代碼時要小心. 一個好建議是:使用#ifdef DEL,而不是簡單地注釋掉甚至是粗暴地直接dd.如果你是使用/* ... */,但一旦要刪除的代碼有很多行,或注釋中以有注釋時,這就可能不那么好使了.
[]如何給源程序文件命名? 表現特色且不與任何原有應用名相同.一個簡單地方法就是試試看,系統有什么樣地反應?
[]一次只修改一個地方.
[]一次只編寫一個單一功能的函數。
[]編寫通用程序. 只有當程序編寫完,并且完成了所需要的性能要求之后,再反過頭來優化該程序.
[]不要使用a.out作為結果.你大可以使用與源程相同的可執行文件名.
[]是否一定要用VI編輯? LINUX下有許多專用編程編輯器.它們能使你有更高的效率和更低的低級輸入錯誤,但我還是要勸你至少要熟練掌握VI.畢竟VI遍地開花.
[]協同作業.請相信,你不是在孤軍作戰.因此,你有必要熟練掌握一些其它的工具.如
.... ....(歡迎您來充實此處空白) ....
第四章:使用lint
lint沒有你想象中的那樣糟糕.相反,一旦源程序形成了沒有LINT錯誤的形式,將很容易保持下去,并享受到如此而帶來的好處.
[]在cc(gcc)之前就應使用LINT. lint是一語法檢查程序,對于這個多嘴的婆婆來說,你應有足夠的耐心.雖然你知道自已在干什么,但在CC之前使用LINT總是一個好習慣.
[]lint有哪些特色? 在編譯之前使用lint的重要原因是LINT不但能發現ANSI C中的語法錯誤,而且也能指出潛在的問題或是難于移植于另一機器的代碼問題.除了能指出簡單語法錯誤之外,LINUT還能基于以下原因指出另外的錯誤: A.無法達到的語句. B.沒有進入循環. C.沒有被使用的變量. D.函數參數從未使用. E.沒有賦值之前自動使用參數. F.函數在有些地方有返回值,但在其他地方不返回. G.函數調用在不同地方使得參數個數不同. H.錯誤使用結構指針. I.模糊使用操作符優先級. 呵呵呵,挺有用的吧!
[]如何控制LINT的輸出? 有時LINT會有一大屏一大屏的警告信息.但似乎并未指出錯誤.為了找出潛在的錯誤則需費心費力地瀏覽這些大量的警告信息. 但如果你的程序會分出幾個獨立的模塊,在初級啟動LINT時不要用可選項.當對這些模塊進行更改或擴充時,可以忽略與代碼無關的某些警告.為此可用以下選擇項: -h 對判別是否有錯,類型是否正確不給出啟發式測試. -v 不管函數中沒有定義的參數 -u 不管被使用的變量和函數沒有定義或定義了但沒有使用.
[]干脆,在程序中插入指令來影響LINT運行.它看樣子有些像注釋. /*NOTREACHED*/ 不可達到的代碼不給信息說明. /*VARARGSn*/ 函數的變量個數不作通常的檢查,只檢查開始n個參數的數據類型. /*NOSTRUCT*/ 對下一個表達式不作嚴格類型檢查. /*ARGUSED*/ 下一函數中,不給出沒被使用參數的警告信息. /*LINTLIBRARY*/ 置于文件的開頭,它將不給出沒被使用函數的警告信息.
關于LINT的更多用法,請用man lint來獲知.
第五章:使用make
[]什么是make?
Unix(Linux)是一個天生的開發平臺,我為此感到高興.make是一個強力的工具.它能自動跟蹤相互依賴的源代碼塊并組成一程序,使得很容易建立一可執行程序.Make就是這種有依賴關系的部分和代碼之間所作的規格說明.
[] 所有的程序都要使用make? 是的.盡管你只有幾個簡單的模塊,但你需要有一種結構來支持它從簡單走向復雜.除非你的程序已經蓋棺定論.
[]Makefile由哪些組成? Makefile由以下幾個部分組成:
注釋. ^^^^ 使用#符號插入.make將忽略#之后的任何內容以及其后的RETURN鍵.
變量. ^^^^ make允許定義與SHELL變量類似的有名變量.比如,你定義了SOURCES=prog.c,那么該變量的值$(SCOURES)就包含了源文件名.
依賴關系. ^^^^^^^^ 左邊是目標模塊,后接一冒號.再接與該模塊有依賴關系的模塊.
命令. ^^^^ 以TAB鍵開始(即使用相同數量的空格也不能代替它).
[]Makefile示例 下面介紹一個簡單的示例來說明make的用法.假設你的程序有兩個源文件main.c和myc.c,一個位於子目錄include下的頭文件myhead.h,一個庫由三個源文件myrout1.c,myrout2.c,myrout3.c產生. 其makefile文件為: #一個基本的MAKEFILE文件. #其中包括個人的頭文件和個人庫. HEADERS=include/myhead.h SOURCES=main.c myc.c PRODUCT=$(HOME)/bin/tool LIB=myrout.a LIBSOURES=myrout1.c myrout2.c myrout3.c CC=cc CFLAGS=-g all:$(PRODUCT) $(PRODUCT):$(SOURCES) $(CC)$(CFLAGS) -o $(PRODUCT)$(SOURCES) lint:$(PRODUCT) lint $(SOURCES)$(LIBSOURCES) 哈哈,挺象SHELL編程的.如果你與我一樣使用LINUX下的gcc,那么只要把上面的CC=cc改為CC=gcc即可.怎么樣,想來一個更復雜點的嗎?
[]一個更為復雜的Makefile 你是否注意到,在上例中,只要啟動make,就會重新編譯所有源代碼. 如果你能看懂以下的makefile,恭喜恭喜,你通關了. #一個更為復雜的makefile HEADERS=include/myhead.h SOURES=main.c myc.c OBJECTS=main.c myc.c PRODUCT=$(HOME)/bin/tool LIB=myrout.a LIBSOURCES=myrout1.c myrout2.c myrout3.c LIBOBJECTS=$(LIB)(myrout1.o)$(LIB)(myrout2.o)$(LIB)(myrout3.o) INCLUDE=include CC=cc CFLAGS=-g -Xc LINT=lint LINTFLAGS=-Xc all:$(PRODUCT) $(PRODUCT):$(OBJECTS)$(LIB) $(CC)(CFLAGS)-o$(PRODUCT)$(OBJECTS)$(LIB) .c.o: $(HEADERS) $(CC)$(CFLAGS) -c I$(INCLUDE)$< $(LIB):$(HEADERS)$(LIBSOURCES) $(CC) $(CFLAGS) -c $(?:.o=.c) ar rv $(LIB) $? rm $? .c.c:; lint: $(PRODUCT) $(LINT)$(LINIFLAGS)$(SOURCES)$LIBSOURCES)
第六章:優質無錯編程
親愛的,檢查一下,你是否注意到了以下的細節?也就是說,你是否是一個合格的,能編寫優質無錯代碼的程序員?要永遠記住,編寫無錯代碼是程序員的責任,而不是測試員.(摘錄于本人的'細節頁',因此本節將永遠不會保持完整,歡迎您來充實她)
[]所有程序員至少出現過的一個錯誤: if(a=3){......}如果a等于3,那么...... 你至少要養成這樣的習慣:當判斷一個變量與一個常量是否相等時,將常量寫在前面.這樣即使你一不小心寫成這樣:if(3=a){......}在cc 之前就可以很容易發現它.
[]老調重彈:邏輯操作符的優先權. 我不愿多嘴.總之,如果你一定要編寫如下代碼時: if(a&0x1&&b&0x2){......} 你的手頭最好有一本詳盡的指南.或者你是這方面的專家.
[]盡量不使用int數據類型. 這僅是一個忠告.你大可使用char,short,long數據類型.若干年以后,當你成長為高手之時,你會發現此時我的良苦用心.
[]對于非整型函數一定要完整定義. 如 long float jisuan(char chArr[],int chNum) { long float lMydata; ... ... return(lMydata); }
[]對于非整型函數的輸入要當心. 如 long float lfNum; ... ... scanf('%lf',&lfNum);
[]float 型的有效數字為7位.當多于7位時,第8位及以后的位將不準確,可以將其定義為long float型.
[]文件的輸入出盡量采用fread fwrite函數.只有當另有用途時才用fprintf fscanf 函數.
[]對于數組及字符串的比較操作時要確認以''結束.
第七章:調試技術
調試技術在本文中不太好說,之所以將其獨立成章是想套用M$的老話:'在下一版本中將會做得更好':-((.其實這類文章在全國各大BBS上滿天飛.
在此我只想說說程序員的應盡職責之一:在程序中使用斷言. ~~~~ []既要維護程序的交付版本,又要維護程序的調試版本,這時可以利用斷言補救.
[]要使用斷言對函數參數進行確認.
[]要從程序中刪除無定義的特性,或者在程序中使用斷言來檢查出無定義特性的非法使用.
[]不要浪費別人的時間,詳細說明不清楚的斷言.
[]消除所做的隱式假定,或者利用斷言檢查其正確性.
[]利用斷言來檢查不可能發生的情況.
一個實例:我在我的源程序中都使用斷言.在本人所編制的全國電子匯兌模糊檢索功能模塊測試中,前臺人員氣喘吁吁地告訴我,屏幕上出現了不認識的英文.我說最前面的是哪幾個數字,然后根據此數字查閱斷言文檔,原來是前日日終未正常結束,經查只有半個庫.這種情況極少發生,但不是不可能發生.使用斷言能及時正確地判別是否是程序的錯誤還是外部的因素.因此使用斷言,是將錯誤消滅在發生錯誤之前的一個極其重要的手法.這也是判斷一個程序員是否具備良好素質的一個方面.