從c轉向c++
對每個人來說,習慣c++需要一些時間,對於已經熟悉c的程式設計師來說,這個過程尤其令人苦惱。因為c是c++的子集,所有的c的技術都可以繼續使用,但很多用起來又不太合適。例如,c++程式設計師會認為指標的指標看起來很古怪,他們會問:為什麼不用指標的引用來代替呢?
c是一種簡單的語言。它真正提供的只有有巨集、指標、結構、陣列和函式。不管什麼問題,c都靠巨集、指標、結構、陣列和函式來解決。而c++不是這樣。巨集、指標、結構、陣列和函式當然還存在,此外還有私有和保護型成員、函式過載、預設引數、構造和析構函式、自定義操作符、內聯函式、引用、友元、模板、異常、名字空間,等等。用c++比用c具有更寬廣的空間,因為設計時有更多的選擇可以考慮。
在面對這麼多的選擇時,許多c程式設計師墨守成規,堅持他們的老習慣。一般來說,這也不是什麼很大的罪過。但某些c的習慣有悖於c++的精神本質,他們都在下面的技巧進行了闡述。
記憶體管理
c++中涉及到的記憶體的管理問題可以歸結為兩方面:正確地得到它和有效地使用它。好的程式設計師會理解這兩個問題為什麼要以這樣的順序列出。因為執行得再快、體積再小的程式如果它不按你所想象地那樣去執行,那也一點用處都沒有。「正確地得到」的意思是正確地呼叫記憶體分配和釋放程式;而「有效地使用」是指寫特定版本的記憶體分配和釋放程式。這裡,「正確地得到」顯得更重要一些。
然而說到正確性,c++其實從c繼承了乙個很嚴重的頭疼病,那就是記憶體洩露隱患。虛擬記憶體是個很好的發明,但虛擬記憶體也是有限的,並不是每個人都可以最先搶到它。
在c中,只要用malloc分配的記憶體沒有用free返回,就會產生記憶體洩露。在c++中,肇事者的名字換成了new和delete,但情況基本上是一樣的。當然,因為有了析構函式的出現,情況稍有改善,因為析構函式為所有將被摧毀的物件提供了乙個方便的呼叫delete的場所。但這同時又帶來了更多的煩惱,因為new和delete是隱式地呼叫建構函式和析構函式的。而且,因為可以在類內和類外自定義new和delete操作符,這又帶來了複雜性,增加了出錯的機會。下面的技巧(還有技巧m8)將告訴你如何避免產生那些普遍發生的問題。
類和函式:設計與宣告
在程式中宣告乙個新類將導致產生一種新的型別:類的設計就是型別設計。可能你對型別設計沒有太多經驗,因為大多數語言沒有為你提供實踐的機會。在c++中,這卻是很基本的特性,不是因為你想去做才可以這麼做,而是因為每次你宣告乙個類的時候實際上就在做,無論你想不想做。
設計乙個好的類很具有挑戰性,因為設計好的型別很具有挑戰性。好的型別具有自然的語法,直觀的語義和高效的實現。在c++中,乙個糟糕的類的定義是無法實現這些目標的。即使乙個類的成員函式的效能也是由這些成員函式的宣告和定義決定的。
那麼,怎麼著手設計高效的類呢?首先,必須清楚你面臨的問題。實際上,設計每個類時都會遇到下面的問題,它的答案將影響到你的設計。
這些都是很難回答的問題,所以c++中定義乙個高效的類遠不是那麼簡單。但如果做好了,c++中使用者自定義的類所產生的型別就會和固定型別幾乎沒什麼區別,如果能達到這樣的效果,其價值也就體現出來了。
上面每乙個問題如果要詳細討論都可以單獨組成一本書。所以後面技巧中所介紹的準則決不會面面俱到。但是,它們強調了在設計中一些很重要的注意事項,提醒一些常犯的錯誤,對設計者常碰到的一些問題提供了解決方案。很多建議對非成員函式和成員函式都適用,所以本章節我也考慮了全域性函式和名字空間中的函式的設計和宣告。
類和函式: 實現
c++是一種高度型別化的語言,所以,給出合適的類和模板的定義以及合適的函式宣告是整個設計工作中最大的一部分。按理說,只要這部分做好了,類、模板以及函式的實現就不容易出問題。但是,往往人們還是會犯錯。
犯錯的原因有的是不小心違反了抽象的原則:讓實現細節可以提取類和函式內部的資料。有的錯誤在於不清楚物件生命週期的長短。還有的錯誤起源於不合理的前期優化工作,特別是濫用inline關鍵字。最後一種情況是,有些實現策略會導致原始檔間的相互聯結問題,它可能在小規模範圍內很合適,但在重建大系統時會帶來難以接受的成本。
所有這些問題,以及與之類似的問題,都可以避免,只要你清楚該注意哪些方面。以下的技巧就指明了應該特別注意的幾種情況。
繼承和物件導向設計
很多人認為,繼承是物件導向程式設計的全部。這個觀點是否正確還有待爭論,但本書其它章節的技巧數量足以證明,在進行高效的c++程式設計時,還有更多的工具聽你調遣,而不僅僅是簡單地讓乙個類從另乙個類繼承。
然而,設計和實現類的層次結構與c語言中的一切都有著根本的不同。只有在繼承和物件導向設計領域,你才最有可能從根本上重新思考軟體系統構造的方法。另外,c++提供了多種很令人困惑的物件導向構造部件,包括公有、保護和私有基類;虛擬和非虛擬基類;虛擬和非虛擬成員函式。這些部件不僅互相之間有聯絡,還和c++的其它部分相互作用。所以,對於每種部件的含義、什麼時候該用它們、怎樣最好地和c++中非物件導向部分相結合 ---- 要想真正理解這些,就要付出艱苦的努力。
使得事情更趨複雜的另乙個原因是,c++中很多不同的部件或多或少地好象都在做相同的事。例如:
在部分的技巧中,我將指導大家怎樣去回答這類問題。當然,我不可能顧及到物件導向設計的方方面面。相反,我將集中解釋的是:c++中不同的部件其真正含義是什麼,當使用某個部件時你真正做了什麼。例如,公有繼承意味著 "是乙個" (詳見技巧35),如果使它成為別的什麼意思,就會帶來麻煩。相似地,虛函式的含義是 "介面必須被繼承",非虛函式的含義是 "介面和實現都要被繼承"。不能區分它們之間的含義會給c++程式設計師帶來無盡的痛苦。
如果能理解c++各種部件的含義,你將發現自己對物件導向設計的認識大大轉變。你將不再停留在為區分c++語言提供的不同部件而苦惱,而是在思考要為你的軟體系統做些什麼。一旦知道自己想做什麼,將它轉化為相應的c++部件將是一件很容易的事。
做你想做的,理解你所做的!這兩點的重要性絕沒有過分抬高。接下來的技巧將對如何高效地實現這兩點進行了詳細的討論。技巧44總結了c++物件導向構造部件間的對應關係和它們的含義。它是本章節最好的總結,也可作為將來使用的簡明參考。
雜項
進行高效的c++程式設計有很多準則,其中有一些很難歸類。本章就是專門為這些準則而安排的。不要因此而小看了它們的重要性。要想寫出高效的軟體,就必須知道:編譯器在背後為你做了些什麼,怎樣保證非區域性的靜態物件在被使用前已經被初始化,能從標準庫得到些什麼,從何處著手深入理解語言底層的設計思想。本書最後的這個章節,我將詳細說明這些問題,甚至更多其它問題。
linux 程式設計實用技巧
1 sizeof 引數為指標的問題。int i int j 10 sizeof i sizeof引數為指標時,返回指標資料型別所佔空間,一般為4 unsigned int 所佔byte sizeof j sizeof引數為陣列時,返回陣列所佔空間,此時為4 10 40 2 多執行緒中需考慮重入的問題...
C 實用技巧(二)
上一篇文章講到了如何檢查記憶體洩露。其實只要肯用c 的stl裡面的高階功能的話,記憶體洩露是很容易避免的。我在開發vczh library 3.0的時候,所有的測試用例都保證跑完了沒有記憶體洩露。但是很可惜有些c 團隊不能使用異常,更甚者不允許寫建構函式析構函式之類,前乙個還好,後乙個簡直就是在用c...
C專家程式設計
說到c語言,首稱 c程式語言 這是一本入門和進修均可的書籍,值得讀三遍以上,方才有味。進修書籍如 c專家程式設計 是一本c語言較高層次的書,是成為c語言專家的必經之路。我自以為達到了一定的 程度,才開始學習這本書的。人言,c語言是一門藝術,需要多年歷練才能達到較為完善的境界,此言不虛。c語言進修書籍...