c++編程規範讀書筆記

                        C++編程規範
Steve McConnell 始終站在代碼閱讀者和使用者的角度去編寫和組織你的程序
(一)   文件結構
頭文件(.h/.hpp):程序聲明
定義文件(.cpp/.cxx):程序實現

 

l  1.1版權和版本的聲明
頭文件開始處應列出:版權說明、程序功能、作者、版本號、生成日期、

與其它文件的關係等。

 

l  1.2頭文件的結構
版權版本聲明

 

預處理塊

 

類結構聲明

 

 

頭文件

 

 

 

爲了防止頭文件被重複引用,用 ifndef/define/endif 結構包含預處理塊。

 

#include <filename.h>引用標準庫頭文件。

 

#include filename.h”引用非標準頭文件。

 

類的聲明頭文件中,儘量將構造函數、析構函數、主要的 public 函數、次要的public 函數、private 函數、private 數據成員按先後順序分層聲明。

 

頭文件中只將頭文件自己必需的 include文件包含進來。

 

除非十分必要(如用內聯函數提高效率),頭文件中只存放“聲明”而不存放“定義”

不提倡使用全局變量,儘量不在頭文件中出現  extern int value;”這類聲明。

l  1.3定義文件的結構
cpp 定義文件中不要 include無關的頭文件。

類的 cpp 定義文件中各個函數的編排順序爲:構造函數、析構函數、主要公有成員函數、次要的公有成員函數、私有成員函數

(二)   程序板式
l  2.1空行
分隔程序段落的作用,使程序佈局更加清晰。空行不會浪費內存,空行的原則:相對獨立的程序塊之間、變量說明之後必須加空行。
在每個類聲明之後、每個函數定義結束之後都要加空行。

在函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。

l  2.2代碼行
不把多個短語句寫在一行中,即一行只寫一條語句,或只定義一個變量(說明:邏輯上緊密關聯的變量放在一行聲明)。這樣的代碼容易閱讀,並且便於寫註釋。

iffordowhilecaseswitchdefault等語句自佔一行,執行

語句不得緊跟其後。不論執行語句有多少都要加{}。這樣可以防止書寫失誤。

 

l  2.3 代碼行內的空格
清晰性.
關鍵字之後要留空格。象 constvirtualinlinecase 等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。象 ifforwhile 等關鍵字之後應留一個空格再跟左括號‘(’,以突出關鍵字。

函數名之後不要留空格,緊跟左括號‘(’,以與關鍵字區別。

‘(’向後緊跟,‘)’、‘,’、‘;’向前緊跟,緊跟處不留空格。

 ‘,’之後要留空格,如 Function(x, y, z)。如果‘;’不是一行的結束符號,其後要留空格,如 for (initialization;  condition;  update)

賦值操作符、比較操作符、算術操作符、邏輯操作符、位域操作符,如“=”、“+= >=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<,^”等二元操作符的前後應當加空格。

一元操作符如“!”、“~”、“++”、“--”、“&”(地址運算符)等前後不加空格。

象“[]”、“.”、“->”這類操作符前後不加空格。

對於表達式比較長的 for 語句和 if 語句,爲了緊湊起見可以適當地去掉一些空格,如 for (i=0; i<10; i++) if ((a<=b)  &&  (c<=d))

l  2.4 對齊
程序的分界符‘{’和‘}’應獨佔一行並且位於同一列,同時與引用它們的語句左對齊。如果出現嵌套的{},則使用縮進對齊。(大多數開發工具已實現自動對齊)

{ }之內的代碼塊在‘{’右邊數格處左對齊。

l  2.5 長行拆分
代碼行不要過長,最大長度宜控制在 80個字符以內。

長表達式要在低優先級操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行適當的縮進,使排版整齊,語句可讀。

 

(三)   標識符命名
l  通用規則
Microsoft “匈牙利”法標識符的命名要清晰、明瞭,有明確含義,可望文知義,可讀性。

l  變量名命名規範
變量的命名規範:範圍描述+類型描述+含義描述。(注:常用的循環控制變量如 i, j, k 等不在此列)

l  其他標識符命名規範
類的名稱以大寫的“C”爲前綴,如 class CHtmlParser

 

結構體類型的命名以“_st”爲前綴

 

FILE 型指針變量以“fp”爲前綴,如 FILE *fpHtmlFile;

 

常量或宏定義全用大寫,用下劃線分割。如const int MAX_LENGTH = 100;

指針類型的變量應用“p”進行標明。如上述的“char *m_pchTextBuf”;若爲指向指針的指針,則用兩個 p,如“char **ppchText;

 

程序中不要出現僅靠大小寫區分的相似的標識符。

 

程序中不要出現名稱完全相同的局部變量,儘管因兩者的作用域不同而不會發生編譯錯誤,但會使人誤解,降低可讀性。

 

用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。

說明:下面是一些在軟件中常用的反義詞組。

add / remove    begin / end        create / destroy        insert / delete

first / last     get / set       increment / decrement     add / delete      

lock / unlock     open / close      min / max            old / new   

start / stop  next / previous     source / target      show / hide  

send / receive     source / destination  cut / paste          up / down

 

儘量避免名字中出現數字編號,如 Value1,Value2 

 

類名和函數名用大寫字母開頭的單詞組合而成。而變量和參數則用小寫字母開頭的單詞組合而成。

(四)   代碼註釋
註釋就是最好的文檔,最有助於對程序的閱讀理解。準確、易懂、簡潔。/*…*///
一般情況下,源程序有效註釋量應在 20%以上,但不宜太多造成喧賓奪

主。如果代碼本來就是清楚的,則不必加註釋。

 

邊寫代碼邊註釋,修改代碼同時修改相應的註釋,保證註釋與代碼的一

致性。不再有用的註釋要刪除。

 

註釋應當準確、易懂,防止註釋有二義性。錯誤的註釋不但無益反而有

害。

註釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,但不可放在下方。如放於上方則需與其上面的代碼用空行隔開。

 

頭文件中的函數聲明前應有詳盡的描述性註釋(功能,參數,返回,備註)。

 

對於所有有物理含義的變量、常量,如果其命名不是充分自注釋的,在聲明時都必須加以註釋,說明其物理含義。

 

數據結構聲明(包括數組、結構體、類、枚舉等),如果其命名不是充分自注釋的,必須加以註釋。

 

全局變量要有較詳細的註釋,包括對其功能、取值範圍、哪些函數存取它以及存取時注意事項等的說明。(建議:最好能避免使用全局變量)。

 

對於 switch 語句下的 case 語句,如果因爲特殊情況需要處理完一個 case後進入下一個 case 處理(即不使用 break),必須在該 case 語句處理完、下一個 case 語句前加上明確的註釋。

(五)   表達式與基本語句
l  運算符的優先級
如果代碼行中的運算符比較多,爲了防止產生歧義並提高可讀性,應當用括號明確表達式的操作順序。

l  複合表達式
 a = b = c = 0  這樣的表達式稱爲複合表達式。
不要編寫太複雜的複合表達式。不要有多用途的複合表達式。不要把程序中的複合表達式與“真正的數學表達式”混淆(ifa<b<c!=if(a<b&&b<c).

 

l  If語句
不可將布爾變量直接與 TRUEFALSE  或者 10進行比較。

 

應當將整型變量用“==”或“!=”直接與 0比較.

 

不可將浮點變量用“==”或“!=”與任何數字比較。(精度問題)

 

應當將指針變量用“==”或“!=”與 NULL  比較。

 

 

對於 if 中的 ==”比較,建議將變量放在“==”之後,如“if (NULL==p)”。這樣可以通過編譯器有效防止少寫一個等號的問題,如果寫成“if(p==NULL)”但少寫一個等號,編譯器難以發現這個問題,而人的肉眼也很難看到,造成的 BUG 很難清除。

l  Case語句
每個 case語句的結尾不要忘了加 break,否則將導致多個分支重疊(除非有意使多個分支重疊,此時必須加上註釋進行說明)。

不要忘記最後那個 default  分支。即使程序真的不需要 default  處理,也應該保留語句 default : break;這樣做並非多此一舉,而是爲了防止別人誤以爲你忘了default處理。

l  Goto語句
爭議不斷,少用、慎用 goto語句,而不是禁用。
l  其他規則建議
避免使用不易理解的數字,用有意義的標識來替代,尤其是代碼中經常重複出現的數字。涉及物理狀態或者含有物理意義的常量,不應直接使用數字,必須用有意義的枚舉或宏常量或 const 常量來代替。這樣可以讓你的代碼變得更可讀、更可靠、並且更容易維護。

 

爲便於代碼的閱讀和理解,源程序中關係較爲緊密的代碼應儘可能相鄰。

 

需要對外公開的常量和宏定義放在頭文件中,不需要對外公開的常量和宏定義放在定義文件的頭部。爲便於管理,可以把不同模塊共用的常量和宏定義集中存放在一個公共的頭文件中。

===========================================================================
(六)   變量與類
l  變量的使用規範
ü  去掉沒必要的或多餘的全局變量。全局變量是增大模塊間耦合的原因之一,應減少沒必要的公共變量以降低模塊間的耦合度。
ü  禁止局部變量與全局變量同名。
ü  在定義變量的同時初始化該變量(就近原則),尤其是指針變量。
ü  嚴禁使用未經初始化的變量作爲右值。特別是引用未經賦值的指針,經常會引起系統崩潰。
ü  在靠近第一次使用變量的位置聲明和初始化該變量。
ü  儘可能縮短變量的“存活”時間,即一個變量存在期間所跨越的語句總數。存活時間很長的變量會降低程序的可讀性,因爲閱讀者在同一時間內需要考慮的代碼行數越多,就越難理解代碼。
2  在循環開始之前再去初始化該循環裏使用的變量,而不是在該循環所在函數的開始處去初始化這些變量。
2  確保使用了所有已聲明的變量,否則就存在多餘的變量,應該去掉。
2  在對變量聲明的同時,應對其含義、作用及取值範圍進行註釋說明,同時若有必要還應說明與其它變量的關係。
2  明確全局變量與操作此全局變量的函數的關係,如訪問、修改及創建等(有利於程序的進一步優化、單元測試、系統聯調以及代碼維護等。這種關係的說明可在註釋或文檔中描述。)
2  當向全局變量傳遞數據時,要十分小心,若有必要應進行合法性檢查,防止賦予不合理的值或越界等現象發生,以提高代碼的可靠性、穩定性。
l  類的使用規範
ü  儘可能限制類中各成員的可訪問性,這是促成類封裝性的原則之一。
ü  要在類的 public 域中暴露成員數據,否則會破壞封裝性,限制你對這個類的控制能力。
ü  防止析構函數忘記釋放內存或其他資源。
ü  消除類中無關的信息,如從未調用過的成員函數或從未用過的數據成員。
2  儘可能在所有的構造函數中初始化所有的數據成員。
2  類的接口應儘可能隱藏其內部實現細節。
2  限制類的數據成員的數目,不應超過 7 個。
l  其他建議
2  編程時,要注意數據類型的強制轉換。
(七)   函數設計
l  函數的參數
ü  參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字。如果函數沒有參數,則用 void  填充;如果函數沒有返回值,也必須用void 填充。
ü  參數命名要恰當並具有“自注釋”效果,順序要合理。
ü  如果參數是指針或引用,且僅作輸入用,則應在類型前加 const,以防止該參數在函數體內被意外修改。
ü  如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高運行效率。
ü  函數中應該使用了所有的參數,如果有一個參數在函數體內未使用,則說明該參數多餘,應去掉(除非有意保留該參數用於以後擴展)。
2  避免函數有太多的參數, 參數個數儘量控制在 5  個以內。如果參數太多,在使用時容易將參數類型或順序搞錯。
2  參數的編排順序一般按照“輸入-修改-輸出”的順序。
2  如果有幾個函數用了類似的一些參數,應該讓這些參數的排列順序保持一致。
l  函數體
ü  在對外提供的 public 函數體的“入口處”,對參數的有效性進行檢查(建議對內部私有的private函數也進行參數有效性檢查,除非你能確保參數取值必定合法)
2  不僅要檢查輸入參數的有效性,還要檢查通過其它途徑進入函數體內的變量的有效性,例如全局變量、文件句柄、成員變量等。
ü  在函數體的“出口處”,對 return 語句的正確性和效率進行檢查。(return 語句不可返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數體結束時被自動銷燬要搞清楚返回的究竟是“值”、“指針”還是“引用”如果函數返回值是一個對象,要考慮 return語句的效率 return String(s1 + s2)String temp(s1 + s2); return temp;效率問題)
ü  對函數體內所調用的其他函數(標準庫函數或用戶自定義的函數)的返回值要仔細、全面地處理,比如檢查 fopen 的返回是否爲 NULL 等。
ü  避免 fopen 之後忘記了 fclose 類似的配對操作,否則將導致IO資源耗盡。
2  函數名應準確描述函數的功能。避免使用無意義或含義不清的動詞如processhandle 等爲函數命名,因爲這些動詞並沒有說明要具體做什麼。
2  保持函數功能的強內聚性,即函數的功能要單一,不要設計多用途的函數。
2  函數體的規模要小,儘量控制在 100  行代碼之內;如果規模過大,可適當地把一段或多段關係密切的代碼拿出來構成一個或多個函數。
2  消除函數中多餘的語句,防止程序中的垃圾代碼。程序中的垃圾代碼不僅佔用額外的空間,而且還會影響程序功能與性能,給程序的測試、維護等造成不必要的麻煩。
2  如果多段代碼重複做同一件事情,那麼在函數的劃分上可能存在問題。(構建新的函數)
2  除非爲某些算法或功能的實現方便,減少函數本身或函數間的遞歸調用。(降低可理解性,佔用較多的系統資源(如棧空間),有可能導致堆棧溢出,難進行程序測試)。
2  儘量避免函數帶有“記憶”功能,相同的輸入應當產生相同的輸出。帶有“記憶”功能的函數,其行爲可能是不可預測的,因爲它的行爲可能取決於某種“記憶狀態”。這樣的函數既不易理解又不利於測試和維護。在 C/C++語言中,函數的 static 局部變量是函數的“記憶”存儲器。建議儘量少用 static  局部變量,除非必需。
2  用於出錯處理的返回值一定要清楚和全面地進行說明,讓使用者不容易忽視或誤解錯誤情況。
l  使用斷言(assert
Assertdebug版本中起作用的宏,檢查“不應該”發生的情況,是無害測試手段。
ü  使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的區別,後者是必然存在的並且是一定要作出處理的。
ü  不能用斷言來檢查最終產品肯定會出現且必須處理的錯誤情況。
2  在函數的入口處,使用斷言檢查參數的有效性(合法性)。
2  在編寫函數時,要進行反覆的考查,並且自問:“我打算做哪些假定?”一旦確定了的假定,就要使用斷言對假定進行檢查。
(八)   內存管理
l  內存分配方式簡介
從靜態存儲區域分配(編譯時分配,程序整個運行期間都存在。如全局變量,static變量。)

 

在棧上創建(函數內局部變量,函數執行結束時這些存儲單元自動被釋放,效率很高,但是分配的內存容量有限)

 

從堆上分配(動態內存分配,malloc/free( 標準庫函數), new/delete(運算符),)

 

 

 

內存分配方式

 

 

 

 

l  常見的內存錯誤及其對策
*  內存分配未成功,卻使用了它。用if (p == NULL)檢查。
*  內存分配雖然成功,但是尚未初始化就引用它。
*  內存分配成功並且已經初始化,但操作越過了內存的邊界。
*  忘記了釋放內存,造成內存泄露。
*  釋放了內存卻繼續使用它。(及時賦值爲NULL
l  內存使用規範
ü  malloc/calloc  new  申請內存之後,應該立即檢查指針值是否爲NULL,防止使用指針值爲NULL  的內存(此時程序將崩潰)。
ü  防止將未被初始化的內存作爲右值使用。
ü  避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”操作。
ü  動態內存的申請與釋放必須配對,防止內存泄漏。
ü  malloc/realloc/calloc申請的內存使用free釋放,new申請的內存使用delete釋放,不能搞混了。
ü  使用new []申請的內存必須用delete[]進行釋放,如果delete沒有加上[]則只調用了數組中第一個對象的析構函數,極可能造成內存泄漏。
ü  free  delete釋放內存後,立即將指針設置爲NULL,防止產生“野指針”。
ü  防止函數內的break/continue/goto/return等語句跳過了必要的內存釋放操作,造成內存泄漏。
(九)   其他編程經驗
l  程序效率方面的考慮
程序效率指的是程序佔用資源的情況,如 CPU、內存、外存、網絡資源等,一般指時間效率和空間效率,前者是指運行速度,後者是指程序佔用內存或者外存的狀況。
ü  不要一味地追求程序的效率,應當在滿足正確性、可靠性、健壯性、可讀性等質量因素的前提下,設法提高程序的效率。
ü  以提高程序的全局效率爲主,提高局部效率爲輔。
ü  在優化程序的效率時,應當先找出限制效率的“瓶頸”,不要在無關緊要之處優化。
ü  先優化數據結構和算法,再優化執行代碼。
ü  有時候時間效率和空間效率可能對立,此時應當分析那個更重要,作出適當的折衷。例如多花費一些內存來提高性能。
ü  不要追求緊湊的代碼,因爲緊湊的代碼並不能產生高效的機器碼。
2  循環體內工作量最小化。應仔細考慮循環體內的語句是否可以放在循環體之外,使循環體內工作量最小,從而提高程序的時間效率。
2  在多重循環中,應將最忙的循環放在最內層,這樣可以減少 CPU 切入循環層的次數,並減少一些語句的執行次數。

 

 

l  一些有益的建議
2  當心那些視覺上不易分辨的操作符發生書寫錯誤。我們經常會把“==”誤寫成“=”,象“||”、“&&”、“<=”、“>=”這類符號也很 容易發生“丟1”失誤。然而編譯器卻不一定能自動指出這類錯誤。
2  當心變量的初值、缺省值錯誤,或者精度不夠。
2  當心變量發生上溢或下溢;當心數組的下標越界。
2  當心忘記編寫錯誤處理程序,當心錯誤處理程序本身有誤。
2  當心文件 I/O  有錯誤。
2  如果原有的代碼質量比較好,儘量複用它。但是不要修補很差勁的代碼,應當重新編寫。
2  儘量使用標準庫函數,不要“發明”已經存在的庫函數。
2  儘量不要使用與具體硬件或軟件環境關係密切的變量。
2  把編譯器的選擇項設置爲最嚴格狀態並重視編譯器給出的每條 warning
2  編寫代碼時要注意隨時保存,並定期備份,防止由於斷電、硬盤損壞等原因造成代碼丟失。
2  同一個軟件產品(或項目組)內,最好使用相同的編譯器,並使用相同的設置選項。
2  用宏定義表達式時,要使用完備的括號。
2  有可能的話,if 語句儘量加上 else 分支,對沒有 else 分支的語句要小心對待;switch 語句必須有 default 分支。
2  不使用與硬件或操作系統關係很大的語句或者第三方提供的非標準類庫如MFC,而使用 C/C++標準庫或語句,以提高軟件的可移植性和可重用性。
(十)   終極建議
永遠記住:“始終站在代碼閱讀者和使用者的角度去編寫和組織你的程序。 

             —— Steve McConnell
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章