個人C++代碼風格

1 前言

  在多人團隊開發中,使用統一的代碼規範,項目開發前期利於團隊協做,中期利於工做審覈,後期利於下降維護成本,總而言之,代碼規範很重要。這話雖然簡單,但一直未能引發重視,直到我看到下圖的那個新聞後,我就知道有不少碼農還活着真不簡單啊。咱們姑且不去深究這個新聞的真實性,但至少咱們也該意識到代碼規範的重要性了吧。html

0001.png

  關於C++的代碼風格,網上有不少,比較權威的有 谷歌華爲 這兩種,我我的推薦使用 華爲 的,這也是我喜歡的代碼風格。之因此選擇 華爲 的,而不是 谷歌 的,主要仍是由於它們造成的背景年代不同。谷歌的代碼風格,不少都受老一輩程序員( Unix 時代的程序員)的影響,他們早期工做的顯示設備,都是低分辨率的屏幕,代碼傾向於壓縮排版,以期待顯示更多內容。而到了如今,咱們工做的絕大多數顯示設備都是高分辨率的屏幕,壓縮排版的代碼佈局,看起來就顯得過於緊湊和密集,這會致使咱們閱讀上的疲勞。相比之下,華爲的代碼風格,在代碼排布上,不少地方常常會使用空格、空行進行分隔,這樣顯得比較寬鬆,閱讀起來下降了疲勞感。git

  在這裏,我不打算重複去談代碼編寫中很細節的代碼風格問題(好比 命名、對齊、縮進 等),而着重於談談我平時在 C++類編寫時的代碼排版方式,以及如何編寫符合 Doxygen 文檔生成規範的代碼註釋,並提一提我在用 IDE(QtCreator、VisualStudio) 編碼時使用的一些技巧來改善代碼的佈局。程序員

2 C++類的佈局基本結構

  一般的C++類編寫,常使用頭文件(*.h、*.hh 等)寫類的聲明,而用源文件(*.cpp、*.cc、*.cxx 等)寫類的實現,固然,對於模板類通常不會這樣將聲明與實現分開兩部分文件編寫,它要麼是聲明與實現合在一塊兒放到頭文件中,要麼是頭文件後用一個(或者多個)內嵌文件(如 *.inl)去編寫類的實現流程。此處,咱們不提模板類的代碼佈局方式(直接參考常規類的代碼佈局方式),而着重談常規類的代碼佈局。github

2.1 類聲明文件(頭文件)

先給出類聲明文件的總體區段排序的摘要,以下所示:編程

  1. 文件說明區段:版權說明、版本號、生成日期、做者、內容、功能、修改日誌等註釋內容。
  2. 類聲明的開始。
  3. 類中,共同屬性內容區段(可選內容,主要填寫一些 類的友元關係、隸屬庫的某些宏聲明 等等)。
  4. 類中,構造函數 與 析構函數 區段。
  5. 類中,通用數據類型的聲明 區段。
  6. 類中,類屬性(靜態屬性)的相關函數與數據成員 區段。
  7. 類中,重載區段:重載父類的接口(虛函數)。
  8. 類中,擴展區段:繼承自該類的子類可重載的接口(虛函數)。
  9. 類中,可對外調用的接口區段(public訪問屬性的成員函數)。
  10. 類中,只對內調用的接口區段(protected訪問屬性的成員函數)。
  11. 類中,類對象的相關數據成員 區段。
  12. 類聲明的結束。

以上區段的排序,在實際項目中,可視具體狀況作調整,也可增長其餘區段。在此,我給出以下的樣例代碼(內容中,以 //=> 開頭的內容,均爲說明性的內容,非代碼中要編寫的內容):ide

//=> 文件說明區段:版權說明、版本號、生成日期、做者、內容、功能、修改日誌等註釋內容。
/**
 * @file    FileName.h
 * <pre>
 * Copyright (c) 2018, Gaaagaa All rights reserved.
 * 
 * 文件名稱:FileName.h
 * 建立日期:2018年11月25日
 * 文件標識:
 * 文件摘要:
 * 
 * 當前版本:1.0.0.0
 * 做    者:
 * 完成日期:2018年11月25日
 * 版本摘要:
 * 
 * 取代版本:
 * 原做者  :
 * 完成日期:
 * 版本摘要:
 * </pre>
 */

//=> 文件頭的守衛宏定義(防止 文件頭重複包含時 產生重複定義)
#ifndef __HEADFILENAME_H__
#ifndel __HEADFILENAME_H__

//=> 包含類設計所依賴的外部頭文件
#include "dependent.h"
#include <dependent.h>

//=> 以 80 個正斜槓字符進行大區段分割,有承前啓後的做用,這裏標識一個大區段(類)內容的開始
////////////////////////////////////////////////////////////////////////////////
// class name  //=> 填寫要設計的類名,表示這個大區段的內容主題

//=> 此處可增長一些類設計所依賴的其餘數據類型、接口函數等的前置聲明

//=> 類的簡要說明(用於 Doxygen 文檔生成操做)
/**
 * @class ClassName
 * @brief 類的簡要描述。
 */
class ClassName : public SuperClass
{
    //=> 類的共同屬性內容區段(可選內容,主要填寫一些 類的友元關係、隸屬庫的某些宏聲明 等等),舉例以下:
    friend class FriendClass;     //=> 友元關係
    Q_OBJECT                      //=> QT 庫的宏聲明(或特性的實現)
    DECLARE_DYNCREATE(ClassName)  //=> 庫的宏聲明(或特性的實現)

    //=> 構造函數 與 析構函數 區段
    // constructor/destructor
public:
    ClassName(void);
    virtual ~ClassName(void);

    //=> 通用數據類型的聲明
    // common data types
public:
    /**
     * @enum  emConstValue
     * @brief 類相關枚舉常量值。
     */
    typedef enum emConstValue
    {
        ECV_CLASS_ID = 0x00000000,
    } emConstValue;

    typedef std::vector<Type> VecType;

    //=> 類屬性(靜態屬性)的相關函數與數據成員
    // common invoking
public:
    //=> 關於 函數/方法 的註釋方式,後面會提到
    /**********************************************************/
    /**
     * @brief 申請對象流水號。
     */
    static int make_object_seqno(void);

protected:
    //=> (類靜態的 或 類對象的)成員變量的註釋,符合 Doxygen 文檔生成的規範兩種註釋
    static int ms_object_seqno_builder;    ///< 用於對象流水號申請操做的生成器  //=> 單行註釋

    /** 所創類對象的計數器 */ //=> 與成員變量不一樣行的註釋,可多行編寫
    static int ms_object_counter;

    //=> 重載區段:重載父類的接口(虛函數)
    // overrides
protected:
    //=> 函數/方法 的註釋方式
    /**********************************************************/ //=> 以此線進行分隔,表示函數的小區段
    /**
     * @brief 重載父類的接口。  //=> 接口的功能 簡要說明
     * 
     * @param [in    ] param_1: 入參形式的參數註釋。     //=> 幾種功能類型的參數註釋(可選段)
     * @param [out   ] param_2: 回參形式的參數註釋。
     * @param [in,out] param_3: 可做入參和回參形式的參數註釋。
     * 
     * @return int            //=> 返回值的註釋(可選段)
     *         - 成功,返回 0;
     *         - 失敗,返回 錯誤碼。
     */
    virtual int super_method(int param_1, int & param_2, int & param_3) override;   //=> override 爲 C++11 後的關鍵字,代碼中可選

    //=> 擴展區段:繼承自該類的子類可重載的接口(虛函數)
    // extensible interfaces
protected:
    //=> 函數/方法 的註釋方式
    /**********************************************************/ //=> 以此線進行分隔,表示函數的小區段
    /**
     * @brief 繼承自該類的子類可重載的接口。  //=> 接口的功能 簡要說明
     * 
     * @param [in    ] param_1: 入參形式的參數註釋。     //=> 幾種功能類型的參數註釋(可選段)
     * @param [out   ] param_2: 回參形式的參數註釋。
     * @param [in,out] param_3: 可做入參和回參形式的參數註釋。
     * 
     * @return int            //=> 返回值的註釋(可選段)
     *         - 成功,返回 0;
     *         - 失敗,返回 錯誤碼。
     */
    virtual int extensible_method(int param_1, int & param_2, int & param_3);

    //=> 可對外調用的接口區段(public訪問屬性的成員函數)
    // public interfaces
public:
    //=> 精簡的 函數/方法 的註釋方式
    /**********************************************************/
    /**
     * @brief 獲取對象標識 ID。
     */
    int get_object_id(void);

    //=> 只對內調用的接口區段(protected訪問屬性的成員函數)
    // inner invoking methods
protected:
    //=> 精簡的 函數/方法 的註釋方式
    /**********************************************************/
    /**
     * @brief 返回計算獲得對象標識 ID。
     */
    int calc_object_id(void);

    //=> 類對象的相關數據成員 區段
    // data members
protected:
    int m_object_id;   ///< 對象標識 ID
};

//=> 以 80 個正斜槓字符進行大區段分割,有承前啓後的做用,這裏標識一個大區段(類)內容的結束
////////////////////////////////////////////////////////////////////////////////

#endif // __HEADFILENAME_H__

2.2 類的實現文件(源文件)

對於類的文件,要描述的區段沒有聲明文件中的多,主要有以下區段:函數

  1. 文件說明區段:版權說明、版本號、生成日期、做者、內容、功能、修改日誌等註釋內容。
  2. 類實現的區段:類屬性(靜態屬性)的相關函數與數據成員。
  3. 類實現的區段:構造函數 與 析構函數 區段。
  4. 類實現的區段:重載區段:重載父類的接口(虛函數)。
  5. 類實現的區段:擴展區段:繼承自該類的子類可重載的接口(虛函數)。
  6. 類實現的區段:可對外調用的接口區段(public訪問屬性的成員函數)。
  7. 類實現的區段:只對內調用的接口區段(protected訪問屬性的成員函數)。

以上區段的排序,在實際項目中,可視具體狀況作調整,也可增長其餘區段。在此,也給出以下的樣例代碼(內容中,以 //=> 開頭的內容,均爲說明性的內容,非代碼中要編寫的內容):工具

//=> 文件說明區段:版權說明、版本號、生成日期、做者、內容、功能、修改日誌等註釋內容。
/**
 * @file    FileName.cpp
 * <pre>
 * Copyright (c) 2018, Gaaagaa All rights reserved.
 * 
 * 文件名稱:FileName.cpp
 * 建立日期:2018年11月25日
 * 文件標識:
 * 文件摘要:
 * 
 * 當前版本:1.0.0.0
 * 做    者:
 * 完成日期:2018年11月25日
 * 版本摘要:
 * 
 * 取代版本:
 * 原做者  :
 * 完成日期:
 * 版本摘要:
 * </pre>
 */

//=> 包含類設計所依賴的外部頭文件
#include "dependent.h"
#include <dependent.h>

//=> 以 80 個正斜槓字符進行大區段分割
////////////////////////////////////////////////////////////////////////////////
// class name  //=> 大區段的內容主題(可多行註釋)

//=> 以 70個字符(「=」)進行分割,表示次級區段
//====================================================================

// 
// ClassName : common invoking ( static methods and data )  //=> 類屬性(靜態屬性)的相關函數與數據成員 區段
// 

//=> 優先聲明靜態成員(或初始化)
int ClassName::ms_object_seqno_builder = 0;  ///< 用於對象流水號申請操做的生成器
int ClassName::ms_object_counter       = 0;  ///< 所創類對象的計數器


//=> 再編寫 靜態函數的實現流程
/**********************************************************/
/**
 * @brief 申請對象流水號。
 */
int ClassName::make_object_seqno(void)
{
    return ++ms_object_seqno_builder;
}

//====================================================================

// 
// ClassName : constructor/destructor   //=> 構造函數 與 析構函數 區段
// 

ClassName::ClassName(void)
{
    m_object_id = calc_object_id();

    ClassName::ms_object_counter += 1;
}

ClassName::~ClassName(void)
{
    ClassName::ms_object_counter -= 1;
}

//====================================================================

// 
// ClassName : overrides   //=> 重載區段:重載父類的接口(虛函數)
// 

/**********************************************************/
/**
 * @brief 重載父類的接口。
 * 
 * @param [in    ] param_1: 入參形式的參數註釋。
 * @param [out   ] param_2: 回參形式的參數註釋。
 * @param [in,out] param_3: 可做入參和回參形式的參數註釋。
 * 
 * @return int
 *         - 成功,返回 0;
 *         - 失敗,返回 錯誤碼。
 */
int ClassName::super_method(int param_1, int & param_2, int & param_3) override
{
    //=> 以 40 個字符(「=」)的短線分割,明確某個代碼塊具有必定的邏輯相關性,便於閱讀與理解
    //======================================
    // 代碼塊說明

    param_2 = param_1 + get_object_id();
    param_3 = param_3 + get_object_id();

    //======================================

    return 0;
}

//====================================================================

// 
// ClassName : extensible interfaces   //=> 擴展區段:繼承自該類的子類可重載的接口(虛函數)
// 

/**********************************************************/
/**
 * @brief 繼承自該類的子類可重載的接口。
 * 
 * @param [in    ] param_1: 入參形式的參數註釋。
 * @param [out   ] param_2: 回參形式的參數註釋。
 * @param [in,out] param_3: 可做入參和回參形式的參數註釋。
 * 
 * @return int
 *         - 成功,返回 0;
 *         - 失敗,返回 錯誤碼。
 */
int ClassName::extensible_method(int param_1, int & param_2, int & param_3)
{
    //======================================
    // 代碼塊說明

    param_2 = param_1 + get_object_id();
    param_3 = param_3 + get_object_id();

    //======================================

    return 0;
}

//====================================================================

// 
// ClassName : public interfaces   //=> 可對外調用的接口區段(public訪問屬性的成員函數)
// 

/**********************************************************/
/**
 * @brief 獲取對象標識 ID。
 */
int ClassName::get_object_id(void)
{
    return m_object_id;
}

//====================================================================

// 
// ClassName : inner invoking   //=> 只對內調用的接口區段(protected訪問屬性的成員函數)
// 

/**********************************************************/
/**
 * @brief 返回計算獲得對象標識 ID。
 */
int ClassName::calc_object_id(void)
{
    return ClassName::make_object_seqno();
}

在上面代碼中, ClassName::super_method() 內部使用了短線分割進行代碼邏輯塊標註,這種方式,在較長代碼流程中很便於代碼的閱讀和理解,我我的的不少代碼中會常用這種方式進行代碼塊的註釋。佈局

3. 其餘數據類型的風格

  C++除了類這種數據類型外,主要的還有結構體、枚舉、C函數這些,這部分基本上屬於原來 C 部分的範疇,只要代碼中的註釋規範符合 Doxygen 的規範便可,其餘的不作過多的說明,只給出簡單的參考樣例:ui

/**
 * @struct StructName
 * @brief  結構體的描述信息說明。
 */
typedef struct StructName
{
    int  v1;  ///< v1 字段說明
    int  v2;  ///< v2 字段說明
    int  v3;  ///< v3 字段說明
} StructName;

/**
 * @enum  emContext
 * @brief 枚舉常量值。
 */
typedef enum emContext
{
    ECV_CONTEXT_VALUE_V1  = 0x0001,   ///< 枚舉值說明
    ECV_CONTEXT_VALUE_V2  = 0x0002,   ///< 枚舉值說明
    ECV_CONTEXT_VALUE_V3  = 0x0003,   ///< 枚舉值說明
    ECV_CONTEXT_VALUE_V4  = 0x0004,   ///< 枚舉值說明
} emContext;

/**********************************************************/
/**
 * @brief 字符串忽略大小寫的比對操做。
 *
 * @param [in ] xszt_lcmp : 比較操做的左值字符串。
 * @param [in ] xszt_rcmp : 比較操做的右值字符串。
 *
 * @return x_int32_t
 *         - xszt_lcmp <  xszt_rcmp,返回 <= -1;
 *         - xszt_lcmp == xszt_rcmp,返回 ==  0;
 *         - xszt_lcmp >  xszt_rcmp,返回 >=  1;
 */
x_int32_t vx_stricmp(x_cstring_t xszt_lcmp, x_cstring_t xszt_rcmp)
{
    x_int32_t xit_lvalue = 0;
    x_int32_t xit_rvalue = 0;

    const x_char_t * xct_lptr = xszt_lcmp;
    const x_char_t * xct_rptr = xszt_rcmp;

    if (xszt_lcmp == xszt_rcmp)
        return 0;
    if (X_NULL == xszt_lcmp)
        return -1;
    if (X_NULL == xszt_rcmp)
        return 1;

    do
    {
        if (((xit_lvalue = (*(xct_lptr++))) >= 'A') && (xit_lvalue <= 'Z'))
        {
            xit_lvalue -= ('A' - 'a');
        }

        if (((xit_rvalue = (*(xct_rptr++))) >= 'A') && (xit_rvalue <= 'Z'))
        {
            xit_rvalue -= ('A' - 'a');
        }

    } while (xit_lvalue && (xit_lvalue == xit_rvalue));

    return (xit_lvalue - xit_rvalue);
}

4. IDE中的代碼編寫技巧

  就我我的使用過的 IDE(針對 C++ 開發的),主要有 Visual C++QtCreator 這兩款,這裏,我也就針對這兩款 IDE 談一談個人一些使用技巧,怎麼對本身編寫的代碼進行註釋和排版。

4.1 Visual C++

  首先,我不得不說,VC++自身的代碼智能提示真的很弱雞,因此我安裝完 VS IDE 後,都會安裝 VisualAssitX 這個插件。按照上面提到個人代碼風格,利用這個插件的代碼片斷快速插入功能,可極大提升代碼編寫的效率。

  VisualAssitX的代碼片斷快速插入功能,按以下步驟,咱們能夠設置自定義的代碼片斷以及對應的快捷鍵:

  1. 依次操做:"VAssistX" => "Visual Assist Options..." => "Visual Assist Options"對話框 => 左"Suggestions" => 右側"Edit VA Snippets"按鈕 => "VA Snippet Editor"對話框;
  2. 調出的 "VA Snippet Editor"對話框 以下圖所示:

    0002.png
  3. Language: 的下拉選擇框中,設置爲 C++Type: 設置爲 All by Shortcut
  4. 點擊工具欄上的第一個按鈕,便可新增「代碼片斷」,在右側可編輯「代碼片斷」的「Shortcut」(快捷輸入的短字符串)和代碼片斷「內容」;在這裏,也可對原有的「代碼片斷」進行修改操做,在完成全部的「代碼片斷」設置後,記得點擊「OK」按鈕保存全部更改的內容。
  5. 步驟3中,Type也能夠選擇其餘的選項,修改一些默認的代碼片斷內容,以適配咱們實際須要;好比,我本身就修改了 Refactoring 下的 Document Method 的內容,知足本身代碼中的函數註解格式。

在這裏列舉幾個我經常使用的代碼片斷(主要用在「註釋」和「代碼區段分割」上):

  • 文件頭部的說明:快捷鍵爲 //-,代碼片斷內容以下
/**
 * @file    $FILE_BASE$.$FILE_EXT$
 * <pre>
 * Copyright (c) $YEAR$, Gaaagaa All rights reserved.
 * 
 * 文件名稱:$FILE_BASE$.$FILE_EXT$
 * 建立日期:$YEAR$年$MONTH_02$月$DAY_02$日
 * 文件標識:
 * 文件摘要:$end$
 * 
 * 當前版本:1.0.0.0
 * 做    者:
 * 完成日期:$YEAR$年$MONTH_02$月$DAY_02$日
 * 版本摘要:
 * 
 * 取代版本:
 * 原做者  :
 * 完成日期:
 * 版本摘要:
 * </pre>
 */
  • 大區段分割線(80 個字符):快捷鍵爲 /// ,代碼片斷以下:
////////////////////////////////////////////////////////////////////////////////
  • 次級區段分割線(70 個字符):快捷鍵爲 //= ,代碼片斷以下:
//====================================================================
  • 函數的簡短註釋:快捷鍵爲 //; ,代碼片斷以下:
/**********************************************************/
/**
 * @brief $end$
 */
  • 邏輯相關(函數內 或 結構內定義)的代碼塊分割線(40 個字符):快捷鍵爲 //=s ,代碼片斷以下:
//======================================
  • 在上面的「步驟5」中,咱們提到修改 Document Method 的內容以下:
/**********************************************************/
/**
 * @brief $end$
 * 
 * @param [in ] $MethodArgName$ : 
 * 
 * @return $SymbolType$
 *         
 */

在編輯代碼時,咱們將光標定位到(放到)函數名中,而後經過以下菜單步驟添加函數的註釋:

"VAssistX" => "Code Generation and Refactoring" => "Document Method"

另外,咱們能夠經過添加全局的組合快捷鍵:Ctrl+Shift+D 來快速調用該菜單命令,設置步驟以下:

1. 菜單 「Tools」 => 「Options...」 => 「Options」 對話框 => 「Environment」 => 「Keyboard」;
2. 在 「Show commands containing:」 輸入:VAssistX.RefactorDocumentMethod ,而後在列表中選中該命令項;
3. 「Use new shortcut in:」 :Global
4. 「Press shortcut keys:」 :Ctrl+Shift+D
5. 點擊 「Assign」 按鈕,而後再點擊 「OK」 按鈕保存設置後退出,這就完成了整個命令的快捷鍵設置流程。

以上提到的代碼片斷,也只是我我的經常使用的一部分,其餘的,視我的習慣與項目狀況,自行配製。VisualAssistX設置的代碼片斷最終會保存到磁盤中的文件:C:\Users\{用戶名}\AppData\Roaming\VisualAssist\Autotext\cpp.tpl ,能夠備份該文件用於其餘狀況的用途(如團隊中使用統一的代碼片斷輸入方式)。我我的的可在此下載cpp.tpl

4.2 QtCreator

  QtCreator沒有VisualAssistX那樣強大的插件,但也提供了代碼片斷快速插入的功能,可經過以下步驟進行配置:

  1. 菜單「Tools」 => 「Options...」 => 「Options」對話框 => 「Text Editor」 => 「Snippets」 選項卡;
  2. 此時可添加相應的代碼片斷,其中「Trigger」 爲代碼編寫過程當中觸發的字符串(至關於快捷鍵);
  3. 完成添加(或修改)操做後,記得保存。

  至於要配置成什麼樣的代碼片斷功能,我這裏就再也不贅述,但就我我的的使用經驗來說,這方面,QtCreator當前版本(4.7.1版)作得還不夠成熟,期待在之後的升級版本中,能有更大改進。

4.3 Doxygen 的代碼文檔生成

  這部分的內容,之後我會在其餘文章中再來詳細寫吧。

5. 參考資料

相關文章
相關標籤/搜索