《高品質C++編程指南》即C++編程規範

來源:互聯網
上載者:User

讀這本書,感覺非常有用.只是有些公用的規則就不一一列舉,只記下自己以前不是那麼清楚地規則.

    代碼品質保證優先原則:
        (1)正確性,指程式要實現設計要求的功能。
        (2)穩定性、安全性,指程式穩定、可靠、安全。
        (3)可測試性,指程式要具有良好的可測試性。
        (4)規範/可讀性,指程式書寫風格、命名規則等要符合規範。
        (5)全域效率,指軟體系統的整體效率。
        (6)局部效率,指某個模組/子模組/函數的本身效率。
        (7)個人表達方式/個人方便性,指個人編程習慣。

    C語言中,static局部變數將在記憶體“資料區”中產生,而非static局部變數將在“堆棧”中產生。
    長語句分多行書寫比:在低優先順序操作符處劃分新行,可使每一行具有相當獨立而完整的含義,從而比較清晰。折行時,操作符要允許存取首。拆分出的新行要進行適當的縮排,使排版整齊,語句可讀。
    如果case 語句中需要定義新的變數,則必須用{}括起來,否則可以不必用{}.
    記憶體釋放後,一定要把指標置為NULL.
    編程時,要防止差1錯誤。
    有可能的話,if語句盡量加上else分支,對沒有else分支的語句要小心對待;switch語句必須有default分支。
    資源檔(多語言版本支援),如果資源是對語言敏感的,應讓該資源與原始碼檔案脫離,具體方法有下面幾種:使用單獨的資源檔、DLL檔案或其它單獨的描述檔案(如資料庫格式)
    某些語句經編譯後產生警示,但如果你確認它是正確的,那麼應通過某種手段去掉警示資訊。
    
    C++語言中,函數的參數和傳回值的傳遞方式有三種:值傳遞、指標傳遞和引用傳遞。
記憶體配置:
    記憶體配置方式有三種:
    (1) 從靜態儲存地區分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在。例如全域變數,static 變數。
    (2) 在棧上建立。在執行函數時,函數內局部變數的儲存單元都可以在棧上建立,函數執行結束時這些儲存單元自動被釋放。棧記憶體配置運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
    (3) 從堆上分配,亦稱動態記憶體分配。程式在啟動並執行時候用 malloc 或 new 申請任意多少的記憶體,程式員自己負責在何時用 free 或 delete 釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多釋放了記憶體卻繼續使用它。
    有三種情況:
    (1)程式中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決對象管理的混亂局面。
    (2)函數的 return 語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用” ,因為該記憶體在函數體結束時被自動銷毀。
    (3)使用 free 或 delete 釋放了記憶體後,沒有將指標設定為 NULL。導致產生“野指標” 。
        char a[] = “hello”;
        a[0] = ‘X’;
        cout << a << endl;
        char *p = “world”;     //  注意 p 指向常量字串
        p[0] = ‘X’;            //  編譯器不能發現該錯誤
        cout << p << endl;
格式:
    在每個類聲明之後、每個函數定義結束之後都要加空行。
空格添加:
    函數名之後不要留空格,緊跟左括弧‘ (’ ,以與關鍵字區別。
    ‘ (’向後緊跟, ‘) ’ 、 ‘, ’ 、 ‘;’向前緊跟,緊跟處不留空格。
    象 if、for、while 等關鍵字之後應留一個空格再跟左括弧‘ (’ ,以突出關鍵字。
    如果‘;’不是一行的結束符號,其後要留空格,如 for (initialization; condition; update)。
    if、for、while、switch等與後面的括弧間應加空格,使if等關鍵字更為突出、明顯。
    逗號、分號只在後面加空格。
    賦值操作符、比較操作符、算術操作符、邏輯操作符、位網域作業符,如“=” 、 “+=”  “>=” 、 “<=” 、 “+” 、 “*” 、 “%” 、 “&&” 、 “||” 、 “<<”,“^”等二元操作符的前後應當加空格。
    一元操作符如“!” 、 “~” 、 “++” 、 “--” 、 “&” (地址運算子)等前後不加空格。
        應當將修飾符 *  和  &  緊靠變數名
        若將修飾符 *  靠近資料類型,例如:int*  x;  從語義上講此寫法比較直觀,即 x 是
        int  類型的指標。
        上述寫法的弊端是容易引起誤解,例如:int*  x, y;  此處 y 容易被誤解為指標變數。
        雖然將 x 和 y 分行定義可以避免誤解,但並不是人人都願意這樣做。
    對於運算式比較長的 for 語句和 if 語句,為了緊湊起見可以適當地去掉一些空格,如 for (i=0; i<10; i++)和 if ((a<=b) && (c<=d))
        不好的實踐:for (i = 0; I < 10; i ++)                 //  過多的空格
注釋:
    對變數的定義和分支語句(條件分支、迴圈語句等)必須編寫注釋。
    好的實踐:當代碼規模較大,邏輯複雜時,先寫注釋,再寫代碼有利於理清思路.
    將注釋與其上面的代碼用空行隔開.
    在命名良好的程式裡可以減少注釋,充分利用代碼的自注釋.
    注釋的目的是解釋代碼的目的、功能和採用的方法,提供代碼以外的資訊,協助讀者理解代碼,防止沒必要的重複注釋資訊。
    通過對函數或過程、變數、結構等正確的命名以及合理地組織代碼的結構,使代碼成為自注釋的。
參數:
    明確規定對介面函數參數的合法性檢查應由函數的調用者負責還是由介面函數本身負責,預設是由函數調用者負責。
    非調度函數應減少或防止控制參數,盡量只使用資料參數。 (本建議目的是防止函數間的控制耦合。調度函數是指根據輸入的訊息類型或控制命令,來啟動相應的功能實體(即函數或過程),而本身並不完成具體功能。控制參數是指改變函數功能行為的參數,即函數要根據此參數來決定具體怎樣工作。非調度函數的控制參數增加了函數間的控制耦合,很可能使函數間的耦合度增大,並使函數的功能不唯一。)
函數:
    功能不明確較小的函數,特別是僅有一個上級函數調用它時,應考慮把它合并到上級函數中,而不必單獨存在。
    當一個過程(函數)中對較長變數(一般是結構的成員)有較多引用時,可以用一個意義相當的宏代替。
      樣本:在某過程中較多引用TheReceiveBuffer[FirstSocket].byDataPtr,則可以通過以下宏定義來代替:# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr
DEBUG:
    同一工程調測列印出的資訊串的格式要有統一的形式。資訊串中至少要有所在模組名(或源檔案名稱)及行號。
    使用斷言來發現軟體問題,提高代碼可測性。
        下面是C語言中的一個斷言,用宏來設計的。(其中NULL為0L)
        #ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
        
            void exam_assert( char * file_name, unsigned int line_no )
            {
                printf( "\n[EXAM]Assert failed: %s, line %u\n",
                file_name, line_no );
                abort( );
            }
        
            #define EXAM_ASSERT( condition )
            if (condition) // 若條件成立,則無動作
                NULL;
            else // 否則報告
                exam_assert( __FILE__, __LINE__ )
        
        #else // 若不使用斷言測試
        
            #define EXAM_ASSERT(condition) NULL
        
        #endif /* end of ASSERT */
迴圈:
    在多重迴圈中,應將最忙的迴圈放在最內層。(說明:減少CPU切入迴圈層的次數。)
    在多重迴圈中,如果有可能,應當將最長的迴圈放在最內層,最短的迴圈放在最外層, 以減少CPU跨切迴圈層的次數。
    避免迴圈體內含判斷語句,應將迴圈語句置於判斷語句的代碼塊之中。(說明:目的是減少判斷次數。迴圈體中的判斷語句是否可以移到迴圈體外,要視程式的具體情況而言,一般情況,與迴圈變數無關的判斷語句可以移到迴圈體外,而有關的則不可以。)
細節:
    盡量用乘法或其它方法代替除法,特別是浮點運算中的除法。(說明:浮點運算除法要佔用較多CPU資源。#define PAI_RECIPROCAL (1 / 3.1416 ) // 編譯器編譯時間,將產生具體浮點數)

////////////////////////////////

標頭檔:
    標頭檔的作用:
        (1)通過標頭檔來調用庫功能。在很多場合,原始碼不便(或不準)向使用者公布,只要向使用者提供標頭檔和二進位的庫即可。使用者只需要按照標頭檔中的介面聲明來調用庫功能,而不必關心介面怎麼實現的。編譯器會從庫中提取相應的代碼。
        (2)標頭檔能加強型別安全檢查。如果某個介面被實現或被使用時,其方式與標頭檔中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程式員調試、改錯的負擔。
    標頭檔中只存放“聲明”而不存放“定義”;標頭檔裡面需要放置定義的情況
    不提倡使用全域變數,盡量不要在標頭檔中出現象 extern int value  這類聲明。 ;Extern作用(參考C++programming)
類:
    (1)將private 類型的資料寫在前面,而將 public 類型的函數寫在後面。採用這種版式的程式員主張類的設計“以資料為中心” ,重點關注類的內部結構。
    (2)將public 類型的函數寫在前面,而將 private 類型的資料寫在後面。採用這種版式的程式員主張類的設計“以行為為中心” ,重點關注的是類應該提供什麼樣的介面(或服務) 。
    全域函數和類的成員函數同名不算重載,因為函數的範圍不同。
    
    全域函數被調用時應加‘::’標誌。如  ::Print(…); //  表示 Print 是全域函數而非成員函數.
        成員函數被重載的特徵:
        (1)相同的範圍(在同一個類中) ;
        (2)函數名字相同;
        (3)參數不同;
        (4)virtual 關鍵字可有可無。
    覆蓋是指衍生類別函數覆蓋基類函數,特徵是:
        (1)不同的範圍(分別位於衍生類別與基類) ;
        (2)函數名字相同;
        (3)參數相同;
        (4)基類函數必須有 virtual 關鍵字。
    “隱藏”是指衍生類別的函數屏蔽了與其同名的基類函數,規則如下:
        (1)如果衍生類別的函數與基類的函數同名,但是參數不同。此時,不論有無 virtual 關鍵字,基類的函數將被隱藏(注意別與重載混淆) 。
        (2)如果衍生類別的函數與基類的函數同名,並且參數也相同,但是基類函數沒有 virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆) 。
        很多 C++程式員沒有意識到有“隱藏”這回事。由於認識不夠深刻,“隱藏”的發生可謂神出鬼沒,常常產生令人迷惑的結果。
        初始設定式表(簡稱初始化表):初始化表位於函數參數表之後,卻在函數體{}之前。這說明該表裡的初始化工作發生在函數體內的任何代碼被執行之前。

    建構函式初始化表的使用規則:
        如果類存在繼承關係,衍生類別必須在其初始化表裡調用基類的建構函式。
        類的 const 常量只能在初始化表裡被初始化,因為它不能在函數體內用賦值的方式來初始化.
        非內部資料類型的成員對象應當採用第一種方式初始化,以擷取更高的效率。

    如果不主動編寫拷貝建構函式和賦值函數,編譯器將以“位拷貝”的方式自動產生預設的函數。倘若類中含有指標變數,那麼這兩個預設的函數就隱含了錯誤,賦值前後對象中的指標變數將指向同一塊記憶體,導致無法正確釋放記憶體。
        String  c = a; //  調用了拷貝建構函式,最好寫成 c(a);
        c = b;  //  調用了賦值函數
        第三個語句的風格較差,宜改寫成 String c(a)以區別於第四個語句。

    如果類之間存在繼承關係,在編寫上述基本函數時應注意以下事項:
        衍生類別的建構函式應在其初始化表裡調用基類的建構函式。
        基類與衍生類別的解構函式應該為虛(即加 virtual 關鍵字)
命名:
    一般來說,長名字能更好地表達含義,所以函數名、變數名、類名長達十幾個字元不足為怪。那麼名字是否越長約好?不見得!  例如變數名 maxval 就比 maxValueUntilOverflow好用。單字元的名字也是有用的,常見的如 i,j,k,m,n,x,y,z 等,它們通常可用作函數內的局部變數。
    Windows應用程式的標識符通常採用“大小寫”混排的方式,如 AddChild。而Unix應用程式的標識符通常採用“小寫加底線”的方式,如 add_child。
    全域函數的名字應當使用“動詞”或者“動詞+名詞” (動賓片語) 。類的成員函數應當只使用“動詞” ,被省略掉的名詞就是對象本身。
    簡單的 Windows 應用程式命名規則
        作者對“匈牙利”命名規則做了合理的簡化,下述的命名規則簡單易用,比較適合於 Windows 應用軟體的開發。
         【規則 3-2-1】類名和函數名用大寫字母開頭的單片語合而成。
        例如:
         class Node;     //  類名
         class LeafNode;    //  類名
         void  Draw(void);   //  函數名
         void  SetValue(int value); //  函數名
         
         【規則 3-2-2】變數和參數用小寫字母開頭的單片語合而成。
        例如:
         BOOL flag;
         int  drawMode;
         
         【規則 3-2-3】常量全用大寫的字母,用底線分割單詞。
        例如:
         const int MAX = 100;
         const int MAX_LENGTH = 100;
         
         【規則 3-2-4】靜態變數加首碼 s_(表示 static) 。
        例如:
        void Init(…)
        {
         static int s_initValue; //  靜態變數
         …
        }
         
         【規則 3-2-5】如果不得已需要全域變數,則使全域變數加首碼 g_(表示 global) 。
        例如:
        int g_howManyPeople; //  全域變數
        int g_howMuchMoney; //  全域變數
        
         【規則 3-2-6】類的資料成員加首碼 m_(表示 member) ,這樣可以避免資料成員與
        成員函數的參數同名。
        例如:
         void Object::SetValue(int width, int height)
         {
          m_width = width;
        m_height = height;
        } 【規則 3-2-7】為了防止某一軟體庫中的一些標識符和其它軟體庫中的衝突,可以為
        各種標識符加上能反映軟體性質的首碼。例如三維圖形標準 OpenGL 的所有庫函數
        均以 gl 開頭,所有常量(或宏定義)均以 GL 開頭
比較:
    不可將布爾變數直接與 TRUE、FALSE 或者 1、0 進行比較。
    不可將浮點變數用“==”或“!=”與任何數字比較.
    應當將整型變數用“==”或“!=”直接與 0 比較。
常量:
    有時我們希望某些常量只在類中有效。const 資料成員的確是存在的,但其含義卻不是我們所期望的。const 資料成員只在某個物件存留期內是常量,而對於整個類而言卻是可變的,因為類可以建立多個對象,不同的對象其 const 資料成員的值可以不同。   不能在類聲明中初始化 const 資料成員。const 資料成員的初始化只能在類建構函式的初始化表中進行.
    如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。

    sizeof(a)的值是 12(注意別忘了’\0’) 。指標 p 指向 a,但是 sizeof(p)的值卻是 4。這是因為 sizeof(p)得到的是一個指標變數的位元組數, 相當於 sizeof(char*), 而不是 p 所指的記憶體容量。 C++/C語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。 注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指標。樣本7-3-3(b)中,不論數組 a 的容量是多少,sizeof(a)始終等於 sizeof(char *)。

    在用 delete 釋放對象數組時,留意不要丟了符號‘[]’ 。例如  
        delete []objects; //  正確的用法
        delete objects; //  錯誤的用法
        後者相當於 delete objects[0],漏掉了另外 99 個對象。

    由於編譯後的名字不同,C++程式不能直接調用 C 函數。C++提供了一個 C 串連交換指定符號 extern“C”來解決這個問題。
        例如:
        extern “C”
        {
           void foo(int x, int y);
           … //  其它函數
        }
        或者寫成
        extern “C”
        {
           #include “myheader.h”
           … //  其它 C 標頭檔
        }
        這就告訴 C++編譯譯器,函數 foo 是個 C 串連,應該到庫中找名字_foo 而不是找_foo_int_int。C++編譯器開發商已經對 C 標準庫的標頭檔作了 extern“C”處理,所以我們可以用#include直接引用這些標頭檔。

    如果函數有多個參數,參數只能從後向前挨個兒預設,否則將導致函數調用語句怪模怪樣。不合理地使用參數的預設值將導致重載函數 output 產生二義性。

用內聯取代宏代碼
    inline 是一種“用於實現的關鍵字” ,而不是一種“用於聲明的關鍵字” 。一般地,使用者可以閱讀函數的聲明,但是看不到函數的定義。對於任何內嵌函式,編譯器在符號表裡放入函數的聲明(包括名字、參數類型、傳回值類型) 。如果編譯器沒有發現內嵌函式存在錯誤,那麼該函數的代碼也被放入符號表裡。在調用一個內嵌函式時,編譯器首先檢查調用是否正確(進行型別安全檢查,或者進行自動類型轉換,當然對所有的函數都一樣) 。如果正確,內嵌函式的代碼就會直接替換函數調用,於是省去了函數調用的開銷。這個過程與預先處理有顯著的不同,因為前置處理器不能進行型別安全檢查,或者進行自動類型轉換。
    定義在類聲明之中的成員函數將自動地成為內嵌函式;將成員函數的定義體放在類聲明之中雖然能帶來書寫上的方便,但不是一種良好的編程風格;將類成員函數放在聲明體外定義,然後再加上inline關鍵字.
    內聯是以代碼膨脹(複製)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內嵌函式的調用都要複製代碼,將使程式的總代碼量增大,消耗更多的記憶體空間。以下情況不宜使用內聯:
        (1)如果函數體內的代碼比較長,使用內聯將導致記憶體消耗代價較高。
        (2)如果函數體內出現迴圈,那麼執行函數體內代碼的時間要比函數調用的開銷大。
    一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯.

初始化列表的效率比在建構函式體內賦值要高效
    先看一下對象建立,對象的建立分兩步:
    1. 資料成員初始化。
    2. 執行被調用建構函式體內的動作。
    當類中存在非基本類型成員變數時,
    會在第一步時首先調用其各個非基本類型成員的建構函式.然後再調用當前類自身的建構函式,此時倘若建構函式中還有賦值操作則需要再次執行一邊賦值函數.
    而使用了初始化列表則不同結果,非基本類型成員僅會在第一步中直接調用其成員類型的帶參建構函式即可.

前置增減效率高

    後置增減操作會隱含產生臨時變數,因為它要儲存操作前的值所為這條語句的值。如果是對基礎類型進行後置增減,在不需要使用操作前的值時,產生臨時變數的動作會被編譯器最佳化掉。不過對於已經重載過的後置操作這種最佳化編譯器很難做到,特別常見的就是使用標準庫時的迭代器自加操作,應該盡量使用前置增減。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.