在多人團隊開發中,使用統一的代碼規範,項目開發前期利於團隊協做,中期利於工做審覈,後期利於下降維護成本,總而言之,代碼規範很重要。這話雖然簡單,但一直未能引發重視,直到我看到下圖的那個新聞後,我就知道有不少碼農還活着真不簡單啊。咱們姑且不去深究這個新聞的真實性,但至少咱們也該意識到代碼規範的重要性了吧。html
關於C++的代碼風格,網上有不少,比較權威的有 谷歌 和 華爲 這兩種,我我的推薦使用 華爲 的,這也是我喜歡的代碼風格。之因此選擇 華爲 的,而不是 谷歌 的,主要仍是由於它們造成的背景年代不同。谷歌的代碼風格,不少都受老一輩程序員( Unix 時代的程序員)的影響,他們早期工做的顯示設備,都是低分辨率的屏幕,代碼傾向於壓縮排版,以期待顯示更多內容。而到了如今,咱們工做的絕大多數顯示設備都是高分辨率的屏幕,壓縮排版的代碼佈局,看起來就顯得過於緊湊和密集,這會致使咱們閱讀上的疲勞。相比之下,華爲的代碼風格,在代碼排布上,不少地方常常會使用空格、空行進行分隔,這樣顯得比較寬鬆,閱讀起來下降了疲勞感。git
在這裏,我不打算重複去談代碼編寫中很細節的代碼風格問題(好比 命名、對齊、縮進 等),而着重於談談我平時在 C++類編寫時的代碼排版方式,以及如何編寫符合 Doxygen 文檔生成規範的代碼註釋,並提一提我在用 IDE(QtCreator、VisualStudio) 編碼時使用的一些技巧來改善代碼的佈局。程序員
一般的C++類編寫,常使用頭文件(*.h、*.hh 等)寫類的聲明,而用源文件(*.cpp、*.cc、*.cxx 等)寫類的實現,固然,對於模板類通常不會這樣將聲明與實現分開兩部分文件編寫,它要麼是聲明與實現合在一塊兒放到頭文件中,要麼是頭文件後用一個(或者多個)內嵌文件(如 *.inl)去編寫類的實現流程。此處,咱們不提模板類的代碼佈局方式(直接參考常規類的代碼佈局方式),而着重談常規類的代碼佈局。github
先給出類聲明文件的總體區段排序的摘要,以下所示:編程
以上區段的排序,在實際項目中,可視具體狀況作調整,也可增長其餘區段。在此,我給出以下的樣例代碼(內容中,以 //=>
開頭的內容,均爲說明性的內容,非代碼中要編寫的內容):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__
對於類的文件,要描述的區段沒有聲明文件中的多,主要有以下區段:函數
以上區段的排序,在實際項目中,可視具體狀況作調整,也可增長其餘區段。在此,也給出以下的樣例代碼(內容中,以 //=>
開頭的內容,均爲說明性的內容,非代碼中要編寫的內容):工具
//=> 文件說明區段:版權說明、版本號、生成日期、做者、內容、功能、修改日誌等註釋內容。 /** * @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()
內部使用了短線分割進行代碼邏輯塊標註,這種方式,在較長代碼流程中很便於代碼的閱讀和理解,我我的的不少代碼中會常用這種方式進行代碼塊的註釋。佈局
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); }
就我我的使用過的 IDE(針對 C++ 開發的),主要有 Visual C++ 和 QtCreator 這兩款,這裏,我也就針對這兩款 IDE 談一談個人一些使用技巧,怎麼對本身編寫的代碼進行註釋和排版。
首先,我不得不說,VC++自身的代碼智能提示真的很弱雞,因此我安裝完 VS IDE 後,都會安裝 VisualAssitX 這個插件。按照上面提到個人代碼風格,利用這個插件的代碼片斷快速插入功能,可極大提升代碼編寫的效率。
VisualAssitX的代碼片斷快速插入功能,按以下步驟,咱們能夠設置自定義的代碼片斷以及對應的快捷鍵:
Language:
的下拉選擇框中,設置爲 C++
,Type:
設置爲 All by Shortcut
;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> */
///
,代碼片斷以下:////////////////////////////////////////////////////////////////////////////////
//=
,代碼片斷以下://====================================================================
//;
,代碼片斷以下:/**********************************************************/ /** * @brief $end$ */
//=s
,代碼片斷以下://======================================
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。
QtCreator沒有VisualAssistX那樣強大的插件,但也提供了代碼片斷快速插入的功能,可經過以下步驟進行配置:
至於要配置成什麼樣的代碼片斷功能,我這裏就再也不贅述,但就我我的的使用經驗來說,這方面,QtCreator當前版本(4.7.1版)作得還不夠成熟,期待在之後的升級版本中,能有更大改進。
這部分的內容,之後我會在其餘文章中再來詳細寫吧。