本文參考 Google開源項目風格指南 ,由于原文篇幅過長,本文對其進行精簡,讀者可以通過右側目錄進行導航閱讀。
本文對一些重點進行了紅色标注 ,同時為了便于了解,還進行了大量舉例。
一.頭檔案
通常每一個
.cc
檔案都有一個對應的
.h
檔案. 也有一些常見例外, 如單元測試代碼和隻包含
main()
函數的
.cc
檔案.
#define 保護
所有頭檔案都應該使用
#define
來防止頭檔案被多重包含, 命名格式當是:
<PROJECT>_``<PATH>``_``<FILE>``_H_
例如:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
…
#endif // FOO_BAR_BAZ_H_
前置聲明
所謂「前置聲明」(forward declaration)是類、函數和模闆的純粹聲明,沒伴随着其定義.
- 盡量避免前置聲明那些定義在其他項目中的實體.
- 函數:總是使用 `#include`.
- 類模闆:優先使用 `#include`.
内聯函數
當函數被聲明為内聯函數之後, 編譯器會将其内聯展開, 而不是按通常的函數調用機制進行調用.
隻有當函數隻有 10 行甚至更少時才将其定義為内聯函數.
包含檔案的名稱及次序
将包含次序标準化可增強可讀性、避免隐藏依賴(hidden dependencies,注:隐藏依賴主要是指包含的檔案編譯),次序如下:
1. 目前cpp檔案對應的.h檔案
1. C 系統檔案
1. C++ 系統檔案
1. 其他庫的 .h 檔案
1. 本項目内 .h 檔案
舉例來說,google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下:
#include “foo/public/fooserver.h” // 優先位置
#include <sys/types.h> //C系統檔案
#include <unistd.h> //C系統檔案
#include <hash_map> /_/C++系統檔案 _
#include _//C++系統檔案 _
#include “base/basictypes.h” //其他庫.h檔案
#include “base/commandlineflags.h” //其他庫.h檔案
#include “foo/public/bar.h” //本項目的.h檔案**
二.作用域
命名空間
命名空間将全局作用域細分為獨立的, 具名的作用域, 可有效防止全局作用域的命名沖突.
- 遵守 命名空間命名 中的規則
- 在命名空間的最後注釋出命名空間的名字
例如:
// .h 檔案
namespace mynamespace {
// 所有聲明都置于命名空間中
// 注意不要使用縮進
class MyClass {
public:
…
void Foo();
};
} // namespace mynamespace
- 不應該使用 _using 訓示_ 引入整個命名空間的辨別符号,例如: ~~using namespace~~~~ ~~~~std~~~~;~~
- 不要在頭檔案中使用 命名空間别名 例如:~~ namespace baz = ::foo::bar::baz;~~
- 禁止用内聯命名空間 例如: ~~inline namespace foo{...}~~
三.類
構造函數的職責
構造函數中不允許調用虛函數,如果代碼允許,應在構造函數出錯直接終止程式,否則使用Init函數進行初始化
删除預設拷貝構造和指派運算符
如果定義的類型明确不允許使用拷貝和指派操作,需要使用delete關鍵字禁用
例如 :
MyClass(const MyClass&) = delete; //禁用拷貝構造
MyClass& operator=(const MyClass&) = delete; //禁用指派運算符
結構體和類的使用時機
僅當隻有資料成員時使用
struct
, 其它一概使用
class
.
繼承
- 所有繼承必須是 `public` 的. 如果你想使用私有繼承, 你應該替換成把基類的執行個體作為成員對象的方式
- 析構函數應聲明為 virtual
- 對于可能被子類通路的成員函數, 不要過度使用 `protected` 關鍵字. 注意, 資料成員都必須是 [私有的](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/classes/#access-control).
- 對于重載的虛函數或虛析構函數, 使用 `override`, 或 (較不常用的) `final` 關鍵字顯式地進行标記
- 不允許多繼承,如果需要多繼承,除第一個類外,其他都應該是接口類型
運算符重載
除少數特定環境外, 不要重載運算符. 也不要建立使用者定義字面量.
四.函數**
- 将所有輸入參數(不修改值的參數)放在輸出參數(會修改值并傳回給調用方的參數)之前,常數參數要使用 const 修飾
- 所有引用參數必須加上const
例如 : void Example(``const int arg1`` ,``const``int& arg2``,void* arg3``);
- 如果需要重載函數,應寫在臨近的代碼位置
- 預設參數應該置于非預設函數之後
五.命名約定
通用命名規則
函數命名, 變量命名, 檔案命名要有描述性; 少用縮寫.
檔案命名
檔案名要全部小寫, 可以包含下劃線 (
_
) 或連字元 (
-
), 依照項目的約定. 如果沒有約定, 那麼 “
_
” 更好.
例如 :
- `my_useful_class.cc`
- `my-useful-class.cc`
類型命名
類型名稱的每個單詞首字母均大寫, 不包含下劃線,如:
MyExcitingClass
,
MyExcitingEnum
變量命名
變量 (包括函數參數) 和資料成員名一律小寫, 單詞之間用下劃線連接配接. 類的成員變量以下劃線結尾, 但結構體的就不用,不使用匈牙利命名法,駝峰命名法
普通變量
如:
a_local_variable
,
a_struct_data_member
,
a_class_data_member_
類成員變量
類成員變量最後應該接下劃線_
如:
class TableInfo {
private:
string table__name__ ; // 好 - 後加下劃線.
string tablename_ ; // 好.
static Pool* pool_ ; // 好.
};
結構體變量
結構體的成員變量可以和普通變量一樣,, 不用像類那樣接下劃線:
如:
struct UrlTableProperties {
string name;
int num_entries;
static Pool* pool;
};
常量命名
常量命名需要以小寫字母“k”開頭,不使用下劃線,單詞首字母大寫,
如: const int kDaysInAWeek = 7;
函數命名
函數命名采用 駝峰命名規則,即單詞首字母大寫,不适用下劃線
如: AddTableEntry()
DeleteUrl()
OpenFileOrDie()
命名空間命名
命名空間以小寫字母命名,并遵守通用命名規則最高層的命名空間的名字取決于項目名稱。命名空間中的代碼,應該存放在和命名空間的名字比對的檔案夾中
枚舉命名
枚舉的命名應和宏一緻,全部使用大寫字母,單詞間使用下劃線分隔
如:enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
宏命名
宏命名全部使用大寫字母,單詞間使用下劃線分隔,如 :
#define ROUND(x) …
#define PI_ROUNDED 3.0
六.注釋
注釋風格
注釋使用 // 或都可以,與團隊保持一緻
檔案注釋
每個檔案開頭應加入版權公告,如果檔案僅僅是一些測試代碼,可以不使用檔案注釋
法律公告和作者資訊
每個檔案都應該包含許可證引用. 為項目選擇合适的許可證版本.(比如, Apache 2.0, BSD, LGPL, GPL)
檔案内容
如果一個
.h
檔案聲明了多個概念(類型), 則檔案注釋應當對檔案的内容做一個大緻的說明, 同時說明各概念之間的聯系. 一個一到兩行的檔案注釋就足夠了, 對于每個概念的詳細文檔應當放在各個概念中, 而不是檔案注釋中.
類注釋
每個類的定義都要附帶一份注釋, 描述類的功能和用法, 除非它的功能相當明顯.
如:
// Iterates over the contents of a GargantuanTable.
// Example:
// GargantuanTableIterator* iter = table->NewIterator();
// for (iter->Seek(“foo”); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTableIterator {
…
};
函數注釋
函數聲明處的注釋描述函數功能; 定義處的注釋描述函數實作
函數聲明
函數聲明處注釋的内容:
- 函數的輸入輸出.
- 對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.
- 函數是否配置設定了必須由調用者釋放的空間.
- 參數是否可以為空指針.
- 是否存在函數使用上的性能隐患.
舉例如下:
// Returns an iterator for this table. It is the client’s
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator* iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;
對于簡單的函數可簡單注釋
函數定義
如果函數的實作比較巧妙或複雜,應該注釋清函數的實作思路
變量注釋
通常變量名本身足以很好說明變量用途. 某些情況下, 也需要額外的注釋說明.
七.C++的一些特性
類型轉換
不要使用強制類型轉換或隐式類型轉換,應該使用C++的類型轉換 ,如 static_cast
自增和自減
對于疊代器和其他模闆對象使用前置形式的自增或自減 ,因為前置自增效率相比後置自增效率更高。如 ++i ,–i,++it,–it 等
預處理宏
- 盡量不要在.h檔案中定義宏
- 盡量在馬上要使用時才進行#define,使用後要立即#undef
- 不要隻是對已經存在的宏使用#undef,選擇一個不會沖突的名稱;
- 不要試圖使用展開後會導緻 C++ 構造不穩定的宏, 不然也至少要附上文檔說明其行為.
- 盡量不要用##處理函數,類和變量的名字
空值,0 , nullptr 和 NULL
- 整數使用0,實數使用0.0
- 指針用nullptr或者NULL
- 字元串使用 '\0'
sizeof的使用
盡量使用sizeof(varname)而不是sizeof(type)
auto的使用
- auto隻能在局部變量裡使用,不要用在檔案作用域
- 如果auto 表達式的右值是一個對象,使用auto& ,例如 auto& person = Person();
- 推薦在疊代周遊中使用 auto
lambda表達式
- 不要使用預設捕獲,捕獲要顯示寫出來 ,比起 `[=](int x) {return x + n;}`, 您該寫成 `[n](int x) {return x + n;}` 才對
- lambda表達式應該盡量簡短,函數體不應過長(不超過10行)
- lambda表達式的傳回值應該以尾置傳回類型的方式寫出來,例如 [](int x, int y)->int{return x+y;};
與零值的判斷
- 整數與零值的判斷應寫為 if (num == 0);
- 浮點數與零值的判斷應寫為 if (num <0.00001&&num>-0.00001); 不能寫成 if(num == 0.0);
- 布爾值與零值判斷應盡量寫為 if(bool == false);而不是if(!bool);