這是一本好書, 可以讓你認清自己對C++的掌握程度.
看完之后,給自己打分,我對C++了解多少? 答案是不足20分.
對於我自己是理所當然的問題, 就不提了, 記一些有啟發的條目和細節:
(*號表示不能完全理解,實力升級了之后回頭看)
一般性問題:
1. 不要在注釋中重復寫代碼語義,這樣很容易產生不一致. 應該編寫的是解釋方法和原理的說明性注釋.
2. 不要對每個項目每個文件進行強制的排版格式規定, 在同一個文件中風格一致即可.
3. 匈牙利記法是混用了類型不安全語言中的設施, 在面向對象語言中可以存在, 但是有害無益, 在泛型編程中則根本不可行, 在規范中禁用該記法則是合理的.
4. 用提倡更短小更簡單的函數來代替規定函數單入口單出口.
5. “定義了從未使用的變量”這個警告可以通過無意義地使用它一下而消除.(如果有必要的話)
6. “遺漏了return語句”可以通過在函數尾部加上一個ASSERT(!”should never get here!”);return -1;來消除警告(!”string”的結果為false)
7. 使用自動構建系統(自動的,可靠的,單操作的)
8. 使用版本控制系統(推薦CVS, 5人日以上的項目都推薦使用CVS)
9. 在代碼審查上投入.(准備一份check list)
a) 良性壓力
b) 提高質量
c) 思想交流
d) 快速培養入門者
e) 形成團隊精神
f) 增強整體實力,提升自信心,動力和職業榮譽感
設計風格(依賴性管理):
10. 一個實體應該只有一個緊湊的職責(變量,類,函數,名稱空間,模塊和庫),隨着實體變大,其職責范圍也會擴大,但不應該發散.
11. 正確,簡單和清晰第一: 正確優於速度. 簡單優於復雜. 清晰優於機巧. 安全優於不安全.
要避免使用程序設計語言中的冷僻特性. 應該使用最簡單的有效技術
12. 編程中應知道何時和如何考慮可伸縮性
a) 使用靈活的,動態分配的數組
b) 了解算法的實際復雜性
c) 優先使用線性算法或者盡可能快的算法
d) 盡可能避免劣於線性復雜性的算法
e) 永遠不要使用指數復雜性的算法
13. 不要進行不成熟的優化: 首先要保持代碼的清晰可讀, 易維護, 易重構.
14. 不要進行不成熟的劣化: 放松自己, 輕松編程. 不要悲觀對自己沒有信心.
a) 可以使用引用傳遞卻用了值傳遞
b) 使用++前綴很適合卻用了后綴
c) 在構造函數中賦值而不是使用初始化列表
d) 使用抽象和庫.
15. 盡量減少全局和共享數據: 避免共享數據,尤其是全局數據. 共享數據會增加耦合度, 從而降低可維護性, 通常還降低性能. 為”無共享”而奮斗吧, 用通信方式(比如消息隊列)來代替數據共享.
16. 隱藏信息: 不要公開提供抽象的實體的內部信息. 絕對不要將類的數據成員設為Public
17. 懂得何時和如何進行並發性編程.(這部分涉及的不多, 沒有多少體會, 回頭補看) 如果應用程序使用了多個線程或者進程, 應該知道如何盡量減少共享對象, 以及如何安全地共享必須共享的對象. 最重要的是避免死鎖, 活鎖和惡性的競爭條件.
18. 確保資源為對象所擁有. 使用顯式的RAII(資源獲取即初始化)和智能指針.
19. 不要在同一條語句內分配一個以上資源.
20. 寧可編譯時和連接時錯誤, 也不要運行時錯誤(這條的論述不理解, 沒概念, 回頭重看.)
21. *積極使用const
22. 避免使用宏: 在C++中可以用const或者enum定義易於理解的常量,用inline避免函數調用的開銷, 用template指定函數系列和類型系列, 用namespace避免名稱沖突.(略去一大堆攻擊宏的敘述.) 但#include #ifdef和assert仍不可避免.
23. 避免使用”魔數”: 用符號名稱和表達式替換他們. 應該用符號常量代替直接寫死的字符串,將字符串與代碼分開(所有字符串放在一個CPP文件中), 這樣非程序員也可以對其進行檢查和更新.
24. 重要的特定於領域的常量可以放在名字空間一級.
25. 特定於類的常量, 可以在類定義中定義靜態整數常量.(在.h中聲明常量,在.cpp中定義值)
26. 盡可能局部地聲明變量. 因為不能合理的初始化變量是c和c++錯誤的普遍來源. 但因為常量並不添加狀態, 所以本條對常量並不適用.
27. 總是初始化變量 :未初始化的變量是C和C++中錯誤的常見來源。養成在使用內存之前先清除的習慣,可以避免這種錯誤,在變量定義的時候就初始化。
28. 使用默認初始值或者?:減少數據流和控制流的混合。
29. 用函數替代復雜的計算流。
30. 初始化數組,正確的初始化並不見得真的要對所有的數據進行操作。
比如:char path[MAX_PATH] = [‘/0’] 創建一個以零填充的空路徑(c風格字符串)。
31. 避免函數過長,避免嵌套過深
a) 盡量緊湊,對一個函數只賦予一種職責
b) 不要自我重復
c) 優先使用&&(相對於if嵌套)
d) 不要過分使用try
e) 優先使用標准算法
f) 不要根據類型標簽(type tag)進行分支(switch),優先使用多態函數
32. 避免跨編譯單元的初始化依賴
33. 盡量減少定義性依賴。避免循環依賴。
a) 依賴倒置原理:不要讓高層模塊依賴於低層模塊。應該讓兩者都依賴於抽象。
34. *頭文件應該自給自足
35. 總是編寫內部#include保護符,絕不要編寫外部#include保護符。(好不容易才理解,累死了,就是說在頭文件本身寫保護符,而不是在包含的時候再用保護符。這樣可以避免保護符名稱不一致。在包含的時候就用不着保護符了。)
36. 正確的選擇通過值、(智能)指針或者引用傳遞參數:分清輸入參數、輸出參數和輸入/輸出參數,分清值參數和引用參數。正確的傳遞參數。
37. 對於只做輸入的參數:
a) 始終用const限制所有指向只輸入參數的指針和引用。
b) 優先通過值來取得原始類型和復制開銷比較低的值的對象。
c) 優先按const的引用取得其他用戶定義類型的輸入。
d) 如果函數需要其參數的副本,則可以考慮通過值傳遞代替通過引用傳遞。這在概念上等同於通過const引用傳遞加上一次復制,能夠幫助編譯器更好的優化掉臨時變量。
38. 對於輸出參數或者輸入/輸出參數:
a) 如果參數是可選的,或者函數需要保存這個指針的副本或者操控參數的所有權,那么優先通過(智能)指針傳遞。
b) 如果參數是必須的,而且函數無需保存指向參數的指針,或者無需操控其所有權,那么應該優先通過引用傳遞。這表明參數是必須的,而且調用者必須提供有效對象。
39. 保持重載操作符的自然語義
40. *優先使用運算操作符和復制操作符的標准形式。
41. 優先使用++和--的標准形式。優先調用前綴形式,因為其少創建了一個對象。這是成熟的優化。
42. 考慮重載以避免隱含類型轉換。(不必要經歷創建臨時變量的麻煩)
43. 避免重載&&、||或,(逗號)(危害一大堆)
a) 內置的&&||符號是短路操作符,遇到false,&&會停止計算,遇到true,||會停止計算。
44. 不要編寫依賴於函數參數求值順序的代碼:函數參數的求值順序是不確定的。
45. *弄清所要編寫的是那種類,然后根據具體情況編寫。
分為:值類 基類 traits 策略類 異常類。
46. 用小類代替巨類。
47. 用組合代替繼承:繼承僅次於友元,是C++中第二緊密的耦合關系。緊密的耦合是一種不良現象,應該盡量避免。(后面還有好處一大堆,但是沒提模式。)
48. 避免從並非要設計成基類的類中繼承:要添加行為,應該添加非成員函數而不是成員函數,要添加狀態,應該使用組合而不是繼承。要避免從具體的基類繼承。
49. 優先提供抽象接口:抽象接口有助於我們集中精力保證抽象的正確性,不至於受到實現或者狀態管理細節的干擾。優先采用實現了(建模抽象概念的)抽象接口的設計層次結構。DIP(Dependency Inversioin Principle 依賴倒置原理)有三個基本的設計優點:更強壯,更靈活,更好的模塊性。
50. 公用繼承即可替換性。繼承不是為了重用,而是為了被重用。
51. 使用繼承的時候,不說“是一個”,而說“其行為像一個”,這樣可以避免描述易於誤解。
52. *實施安全的改寫。
53. *考慮將虛擬函數聲明為非公用的,將公用函數聲明為非虛擬的。
54. *要避免提供隱式轉換
55. *將數據成員設為私有的, 無行為的聚集(C語言形式的struct)
56. *不要公開內部數據
57. *明智地使用Pimpl
58. *優先編寫非成員非友元函數
59. *總是一起提供new和delete(這里指重載它們之一時)
60. *如果提供類專門的new, 應該提供所有標准形式. (普通,就地和不拋出)
61. 以同樣的順序定義和初始化成員變量
62. 在構造函數中用初始化代替賦值.
63. 避免在構造函數和析構函數中調用虛擬函數(*可以使用”后構造函數”來實現類似目的)
64. *將基類析構函數設為公用且虛擬的, 或者保護且非虛擬的.
65. *析構函數,釋放和交換絕對不能失敗.
66. 一致地進行賦值和銷毀.如果定義了賦值構造函數, 復制賦值操作符或者析構函數中的任何一個, 那么可能也需要定義另一個或另外兩個.
67. 顯式地啟用或者禁止復制.
a) 如果賦值對你的類型沒有意義, 那就通過將它們聲明為私有的未實現函數來禁止復制構造和復制賦值.
b) 如果復制和復制賦值適合於T對象,但是正確的復制行為又不同於編譯器生成的版本, 那么就自己編寫函數並將他們設為私有的.
c) 使用編譯器生成的版本,最好是加上一個明確的注釋.
68. *避免切片. 在基類中考慮用克隆代替復制.
69. *使用標准的賦值形式. 在實現operator=時, 應該使用標准形式—具有特定簽名的非虛擬形式.
70. *只要可行,就提供不會失敗的swap (而且要正確的提供)
71. 將類型及其非成員函數接口置於同一名字空間中.
72. *應該將類型和函數分別置於不同的名字空間中,除非有意想讓它們一起工作.
73. 不要在頭文件中或者#include之前編寫名字空間using
74. 要避免在不同的模塊中分配和釋放內存
75. *不要在頭文件中定義具有鏈接的實體
76. *不要允許異常跨越模塊邊界傳播.
77. *在模塊的接口中使用具有良好可移植性的類型
78. *理智的結合靜態多態性和動態多態性
79. 多態性的優勢在於,同一段代碼能夠操作不同類型,甚至可以是在編寫代碼時不知道的類型.
80. *有意的進行顯式自定義
81. *不要特化模板函數
82. 不要無意地編寫不通用的代碼
a) 使用!=代替<對迭代器進行比較
b) 使用迭代替代索引訪問
c) 使用empty()代替size()==0
d) 使用層次結構中最高層的類提供需要的功能
e) 編寫常量正確的代碼
83. 廣泛的使用斷言記錄內部假設和不變式
84. 千萬不要在assert語句中編寫具有副作用的表達式
85. 建立合理的錯誤處理策略, 並嚴格遵守. 包括
a) 鑒別: 哪些情況屬於錯誤
b) 嚴重程度: 每個錯誤的嚴重或者緊急性
c) 檢查: 哪些代碼負責檢查錯誤
d) 傳遞: 用什么機制在模塊中報告和傳遞錯誤通知
e) 處理: 哪些代碼負責處理錯誤
f) 報告: 怎樣將錯誤記入日志, 或通知用戶.
86. *區別錯誤與非錯誤
87. *設計和編寫錯誤安全代碼
88. *優先使用異常報告錯誤
89. 通過值拋出, 通過引用捕獲
90. *正確地報告, 處理和轉換錯誤
91. *避免使用異常規范
92. 默認時使用vector. 否則, 選擇其他適合的容器
a) 編程時正確,簡單和清晰是第一位的
b) 編程時只在必要時才考慮效率
c) 盡可能編寫事務性的. 強錯誤安全的代碼
93. vector具有如下性質:
a) 保證所有容器中最低的空間開銷
b) 保證具有所有容器中對所存放元素進行存取的速度最快
c) 保證與生俱來的引用局部性,也就是說容器中相鄰對象保證在內存中也相鄰,這是其它標准容器無法保證的
d) 保證具有C語言兼容的內存布局,這與其它標准容器也不同
e) 保證具有所有容器中最靈活的迭代器(隨機訪問迭代器)
f) 幾乎肯定具有最快的迭代器(指針,或性能向但與類,在非調試模式下迭代 器類經常可以被編譯成與指針具有相同的速度),比其他所有容器的迭代器都快.
94. 用vector和string代替數組
a) 它們能夠自動管理內存
b) 它們具有豐富的接口
c) 它們與C內存模型兼容
d) 它們能夠提供更大范圍的檢查
e) 它們支持上述特性並未犧牲太多效率
f) 它們有助於優化
95. *使用vector(和string::c_str)與非C++API交換數據
96. *在容器中只存儲值和智能指針
97. *用push_back代替其它擴展序列的方式
98. 多用范圍操作, 少用單元素操作
99. *使用公認的慣用法真正的壓縮容量,真正地刪除元素
100. 算法即循環—只是更好. 算法是循環的”模式”, 在只用for語句實現的原始循環上增加了很多語義內容和其他豐富內容.
101. *使用帶檢查的STL實現
102. *用算法調用代替手工編寫的循環
a) 提高交流層次
b) 正確性, 效率較好.
103. *使用正確的STL排序算法
a) Partition, stable_partition和nth_element算法是線性時間的.
b) Partial_sort, sort, stable_sort和nth_element需要隨機訪問迭代器.
104. *使謂詞成為純函數
a) 謂詞就是返回是或否的函數對象. 從數學意義上來說, 如果函數的結果只取決於其參數,則該函數就是一個純函數.
105. *算法和比較器的參數應多用函數對象少用函數
106. *正確編寫函數對象
107. 避免使用類型分支,多使用多態
108. *依賴類型, 而非其表示方式
a) 不要對對象在內存中的准確表示方式做任何假設. 相反應該讓類型決定如何在內存中讀寫其對象.
109. 避免使用reinterpret_cast
110. 避免對指針使用static_cast
a) 用dynamic_cast, 重構, 甚至重新設計來代替它.
111. 避免強制轉換const
112. *不要使用c風格的強制轉換
113. 不要對非POD進行memcpy或者memcmp操作
114. *不要使用聯合重新解釋表示方式
115. 不要使用可變長參數(…)
116. 不要使用失效對象. 不要使用不安全函數
a) 已銷毀對象
b) 語義失效對象
c) 從來都有效的對象
117. *不要多態地處理數組
