序
C++用法很多,包容性也比較強(qiáng)寿弱。一個(gè)C++的工程可能包含了各種各樣沒見過的用法渗磅。本篇內(nèi)容主要是參照谷歌C++標(biāo)準(zhǔn)規(guī)范嚷硫,結(jié)合自身實(shí)際工作
及經(jīng)驗(yàn)检访,整理一份適合平時(shí)C++開發(fā)的規(guī)則,規(guī)范自身C++編程規(guī)范仔掸。詳細(xì)內(nèi)容可參考Google C++風(fēng)格指南脆贵。
1 函數(shù)
1.1 參數(shù)順序
總述
函數(shù)的參數(shù)順序?yàn)? 輸入?yún)?shù)在先, 后跟輸出參數(shù).
說明
C/C++ 中的函數(shù)參數(shù)或者是函數(shù)的輸入, 或者是函數(shù)的輸出, 或兼而有之. 輸入?yún)?shù)通常是值參或 const引用, 輸出參數(shù)或輸入/輸出參數(shù)則一般為非 const 指針. 在排列參數(shù)順序時(shí), 將所有的輸入?yún)?shù)置于輸出參數(shù)之前. 特別要注意, 在加入新參數(shù)時(shí)不要因?yàn)樗鼈兪切聟?shù)就置于參數(shù)列表最后, 而是仍然要按照前述的規(guī)則, 即將新的輸入?yún)?shù)也置于輸出參數(shù)之前.
這并非一個(gè)硬性規(guī)定. 輸入/輸出參數(shù) (通常是類或結(jié)構(gòu)體) 讓這個(gè)問題變得復(fù)雜. 并且, 有時(shí)候?yàn)榱似渌瘮?shù)保持一致, 你可能不得不有所變通.
1.2 編寫簡(jiǎn)短函數(shù)
總述
我們傾向于編寫簡(jiǎn)短, 凝練的函數(shù).
說明
我們承認(rèn)長(zhǎng)函數(shù)有時(shí)是合理的, 因此并不硬性限制函數(shù)的長(zhǎng)度. 如果函數(shù)超過 40 行, 可以思索一下能不能在不影響程序結(jié)構(gòu)的前提下對(duì)其進(jìn)行分割.
即使一個(gè)長(zhǎng)函數(shù)現(xiàn)在工作的非常好, 一旦有人對(duì)其修改, 有可能出現(xiàn)新的問題, 甚至導(dǎo)致難以發(fā)現(xiàn)的 bug. 使函數(shù)盡量簡(jiǎn)短, 以便于他人閱讀和修改代碼.
在處理代碼時(shí), 你可能會(huì)發(fā)現(xiàn)復(fù)雜的長(zhǎng)函數(shù). 不要害怕修改現(xiàn)有代碼: 如果證實(shí)這些代碼使用 / 調(diào)試起來很困難, 或者你只需要使用其中的一小段代碼, 考慮將其分割為更加簡(jiǎn)短并易于管理的若干函數(shù).
1.3 引用參數(shù)
總述
所有按引用傳遞的參數(shù)必須加上 const.
定義
在 C 語(yǔ)言中, 如果函數(shù)需要修改變量的值, 參數(shù)必須為指針, 如 int foo(int *pval). 在 C++ 中, 函數(shù)還可以聲明為引用參數(shù): int foo(int &val).
優(yōu)點(diǎn)
定義引用參數(shù)可以防止出現(xiàn) (*pval)++ 這樣丑陋的代碼. 引用參數(shù)對(duì)于拷貝構(gòu)造函數(shù)這樣的應(yīng)用也是必需的. 同時(shí)也更明確地不接受空指針。
缺點(diǎn)
容易引起誤解, 因?yàn)橐迷谡Z(yǔ)法上是值變量卻擁有指針的語(yǔ)義起暮。
結(jié)論
函數(shù)參數(shù)列表中, 所有引用參數(shù)都必須是 const:
void Foo(const string &in, string *out);
1.4 函數(shù)重載
總述
若要使用函數(shù)重載, 則必須能讓讀者一看調(diào)用點(diǎn)就胸有成竹, 而不用花心思猜測(cè)調(diào)用的重載函數(shù)到底是哪一種. 這一規(guī)則也適用于構(gòu)造函數(shù)卖氨。
定義
你可以編寫一個(gè)參數(shù)類型為 const string& 的函數(shù), 然后用另一個(gè)參數(shù)類型為 const char* 的函數(shù)對(duì)其進(jìn)行重載:
class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};
優(yōu)點(diǎn)
通過重載參數(shù)不同的同名函數(shù), 可以令代碼更加直觀. 模板化代碼需要重載, 這同時(shí)也能為使用者帶來便利。
缺點(diǎn)
如果函數(shù)單靠不同的參數(shù)類型而重載 (acgtyrant 注:這意味著參數(shù)數(shù)量不變), 讀者就得十分熟悉 C++ 五花八門的匹配規(guī)則, 以了解匹配過程具體到底如何. 另外, 如果派生類只重載了某個(gè)函數(shù)的部分變體, 繼承語(yǔ)義就容易令人困惑.
結(jié)論
如果打算重載一個(gè)函數(shù), 可以試試改在函數(shù)名里加上參數(shù)信息. 例如, 用 AppendString() 和 AppendInt() 等, 而不是一口氣重載多個(gè) Append(). 如果重載函數(shù)的目的是為了支持不同數(shù)量的同一類型參數(shù), 則優(yōu)先考慮使用 std::vector 以便使用者可以用 列表初始化 指定參數(shù)鞋怀。
1.5 函數(shù)返回類型后置語(yǔ)法
總述
只有在常規(guī)寫法 (返回類型前置) 不便于書寫或不便于閱讀時(shí)使用返回類型后置語(yǔ)法.
定義
C++ 現(xiàn)在允許兩種不同的函數(shù)聲明方式. 以往的寫法是將返回類型置于函數(shù)名之前. 例如:
int foo(int x);
C++11 引入了這一新的形式. 現(xiàn)在可以在函數(shù)名前使用 auto 關(guān)鍵字, 在參數(shù)列表之后后置返回類型. 例如:
auto foo(int x) -> int;
后置返回類型為函數(shù)作用域. 對(duì)于像 int 這樣簡(jiǎn)單的類型, 兩種寫法沒有區(qū)別. 但對(duì)于復(fù)雜的情況, 例如類域中的類型聲明或者以函數(shù)參數(shù)的形式書寫的類型, 寫法的不同會(huì)造成區(qū)別.
優(yōu)點(diǎn)
后置返回類型是顯式地指定 Lambda 表達(dá)式 的返回值的唯一方式. 某些情況下, 編譯器可以自動(dòng)推導(dǎo)出 Lambda 表達(dá)式的返回類型, 但并不是在所有的情況下都能實(shí)現(xiàn). 即使編譯器能夠自動(dòng)推導(dǎo), 顯式地指定返回類型也能讓讀者更明了.
有時(shí)在已經(jīng)出現(xiàn)了的函數(shù)參數(shù)列表之后指定返回類型, 能夠讓書寫更簡(jiǎn)單, 也更易讀, 尤其是在返回類型依賴于模板參數(shù)時(shí). 例如:
template <class T, class U> auto add(T t, U u) -> decltype(t + u);
對(duì)比下面的例子:
template <class T, class U> decltype(declval<T&>() + declval<U&>()) add(T t, U u);
缺點(diǎn)
后置返回類型相對(duì)來說是非常新的語(yǔ)法, 而且在 C 和 Java 中都沒有相似的寫法, 因此可能對(duì)讀者來說比較陌生.
在已有的代碼中有大量的函數(shù)聲明, 你不可能把它們都用新的語(yǔ)法重寫一遍. 因此實(shí)際的做法只能是使用舊的語(yǔ)法或者新舊混用. 在這種情況下, 只使用一種版本是相對(duì)來說更規(guī)整的形式.
結(jié)論
在大部分情況下, 應(yīng)當(dāng)繼續(xù)使用以往的函數(shù)聲明寫法, 即將返回類型置于函數(shù)名前. 只有在必需的時(shí)候 (如 Lambda 表達(dá)式) 或者使用后置語(yǔ)法能夠簡(jiǎn)化書寫并且提高易讀性的時(shí)候才使用新的返回類型后置語(yǔ)法. 但是后一種情況一般來說是很少見的, 大部分時(shí)候都出現(xiàn)在相當(dāng)復(fù)雜的模板代碼中, 而多數(shù)情況下不鼓勵(lì)寫這樣復(fù)雜的模板代碼.
2 命名約定
2.1 通用命名規(guī)則
盡可能使用描述性的命名双泪,不要用只有項(xiàng)目開發(fā)者才能理解的縮寫,也不能通過砍掉字母來縮寫單詞(除非是程序員都熟悉的縮寫)密似。
2.2 文件命名
總述
文件名要全部小寫,多個(gè)單詞用下劃線或者’-’連接葫盼。推薦使用””残腌。
說明
可接受的文件名命名示例:
my_useful_class.cpp
my-useful-class.cpp
不要使用已經(jīng)存在于 /usr/include 下的文件名 (Yang.Y 注: 即編譯器搜索系統(tǒng)頭文件的路徑), 如 db.h.
通常應(yīng)盡量讓文件名更加明確. http_server_logs.h就比 logs.h 要好. 定義類時(shí)文件名一般成對(duì)出現(xiàn), 如 foo_bar.h 和 foo_bar.cc, 對(duì)應(yīng)于類 FooBar.
內(nèi)聯(lián)函數(shù)必須放在 .h 文件中. 如果內(nèi)聯(lián)函數(shù)比較短, 就直接放在 .h 中.
2.3 類型命名
總述
類型名稱的每個(gè)單詞首字母均大寫, 不包含下劃線:MyExcitingClass, MyExcitingEnum.
說明
所有類型命名 —— 類, 結(jié)構(gòu)體, 類型定義 (typedef), 枚舉, 類型模板參數(shù) —— 均使用相同約定, 即以大寫字母開始, 每個(gè)單詞首字母均大寫, 不包含下劃線. 例如:
// 類和結(jié)構(gòu)體
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// 類型**定義**
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// using 別名
using PropertiesMap = hash_map<UrlTableProperties *, string>;
// 枚舉
enum UrlTableErrors { ...
2.4 變量命名
總述
變量 (包括函數(shù)參數(shù)) 和數(shù)據(jù)成員名一律小寫, 單詞之間用下劃線連接. 類的成員變量以m_開始, 但結(jié)構(gòu)體的就不用, 如: a_local_variable, a_struct_data_member, m_class_data_member.
變量標(biāo)識(shí)符與對(duì)應(yīng)的類型標(biāo)識(shí)符之間的區(qū)別應(yīng)避免僅在于用小寫字母寫的初始字母不同情況。eg:Path path;是不可取的
說明
普通變量命名
舉例:
string table_name; // 好 - 用下劃線.string tablename; // 好 - 全小寫.
string tableName; // 差 - 混合大小寫
類數(shù)據(jù)成員
不管是靜態(tài)還是非靜態(tài)的贫导,類數(shù)據(jù)成員都可以和普通變量一樣抛猫,但要加m或者m_前綴。
若是m前綴則m后緊跟大寫字母孩灯;若使用m_闺金,后接小寫字母。
m_用法:后接下劃線和全小寫
class TableInfo {
...
private:
string m_table_name; // 好 - 后加下劃線.
string m_tablename; // 好.
static Pool<TableInfo>* m_pool; // 好.
};
m用法:后接首字母大寫的獨(dú)立單詞
class TableInfo {
...
private:
string mTableName; // 好 - 后加下劃線.
string mTableName; // 好.
static Pool<TableInfo>* mPool; // 好.
};
推薦使用第一種7宓怠败匹!
結(jié)構(gòu)體變量
不管是靜態(tài)的還是非靜態(tài)的, 結(jié)構(gòu)體數(shù)據(jù)成員都可以和普通變量一樣, 不用像類成員那樣接下劃線。
struct UrlTableProperties {
string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};
全局變量
總述
一般情況下禁止使用全局變量讥巡,非不得已情況下采用g_前綴掀亩,其他格式與普通變量相同。
說明
凡是需要采用全局變量extern欢顷,盡量都優(yōu)化為get槽棍,set修改內(nèi)部static靜態(tài)變量。
int g_value;
char g_name[] = {};
注:全局變量和靜態(tài)全局變量均采用此命名格式抬驴。
指針變量
總述
采用指針變量時(shí)要格外小心炼七,盡量在聲明時(shí)就初始化。避免程序中使用未初始化的野指針布持,從而導(dǎo)致程序崩潰豌拙。
說明
指針變量采用“駝峰”命名規(guī)則,即小寫p前綴鳖链、大小寫混合姆蘸、單詞首字母大寫墩莫。
char name[] = “C++ Style”;
char *pStr, *pName = name[0];
pStr = name;
2.5 常量命名
在程序代碼中,所有固定的數(shù)值(可能除了- 1,0和1)都要用常量替換逞敷,也就是說狂秦,不使用“神奇的數(shù)字”(這些數(shù)字后來都不知道這個(gè)值代表什么)常量不允許使用小寫字母,因此名稱部分之間的下劃線在這里是允許的推捐。
const double PI = 3.14
const int MAX_SIZE = 100;
注: 僅在cpp文件中使用的常量裂问,建議定義在cpp文件中以常量的形式代替宏。若是定義的常量供多個(gè)文件使用牛柒,建議以宏或者枚舉的形式在頭文件中聲明堪簿,避免編譯報(bào)重定義警告。
2.6 函數(shù)命名
總述
常規(guī)函數(shù)使用大小寫混合, 取值和設(shè)值函數(shù)則要求與變量名匹配:
MyExctingFunction();
MyExcitingMethod();
my_exciting_member_variable();
set_my_exciting_member_variable();
說明
推薦使用皮壁,函數(shù)名每個(gè)單詞首字母都需要大寫椭更,沒有下劃線。對(duì)于首字母縮寫的單詞蛾魄,更傾向于將它們視作一個(gè)單詞進(jìn)行首字母大寫 (例如, 寫作StartRpc()而非StartRPC())虑瀑。
AddTableEntry();
DeleteUrl();
OpenFileOrDie();
取值和設(shè)值函數(shù)的命名與變量一致. 一般來說它們的名稱與實(shí)際的成員變量對(duì)應(yīng), 但并不強(qiáng)制要求. 例如 int count()與void SetCount(int count)。
2.7 命名空間命名
2.8 枚舉命名
總述
枚舉的命名應(yīng)當(dāng)于常量和宏一致滴须,以大寫E字母開頭舌狗,多個(gè)單詞用下劃線_連接。ESIZE_SEARCH扔水。
說明
由于枚舉的功能與宏功能類似痛侍,故規(guī)定枚舉命名規(guī)則與宏命名一致。為避免與宏命名沖突魔市,規(guī)定枚舉命名以大寫字母E作為前綴主届,使得枚舉命名更加清晰。
enum EFormatStateExtPvr
{
FORMAT_STATE_PROGRESS = 0x1,
FORMAT_STATE_NOTSET= 0xF
};
2.9 宏命名
總述
谷歌規(guī)則中推薦使用內(nèi)聯(lián)函數(shù)嘹狞、枚舉和常量代替宏的使用岂膳。
說明
宏的定義規(guī)則與枚舉一樣,只不過不需要任何字母前綴磅网,大寫以下劃線_連接谈截。
#define PI 3.14
#define MAX_SIZE 1024
#define ROUND(x) ...
宏定義中的參數(shù),可以使用小寫涧偷。
3 注釋
注釋的重要性不亞于代碼實(shí)現(xiàn)簸喂,好的注釋能夠讓代碼可讀性更強(qiáng)。優(yōu)秀的注釋能減輕開發(fā)人員自身的負(fù)擔(dān)燎潮,提高團(tuán)隊(duì)開發(fā)效率喻鳄。當(dāng)然也要注意:注釋固然重要,但最好的注釋就是代碼本身确封,有意義的類型名和變量名除呵,要遠(yuǎn)勝于用注釋解釋含糊不清的名字再菊。
3.1 注釋風(fēng)格
總述
使用// 或 /* */,都是允許的,只要統(tǒng)一即可颜曾。內(nèi)容語(yǔ)言建議使用英文纠拔。
說明
本文推薦單行注釋采用//,多行注釋采用/* */形式。若工程中已經(jīng)存在注釋風(fēng)格泛豪,需要與當(dāng)前工程保持一致即可稠诲。
3.2 文件注釋
總述
在每一個(gè)文件的開頭加入版權(quán)公告。
文件注釋應(yīng)包括版權(quán)诡曙、文件名臀叙、作者、版本价卤、描述劝萤、日志、注釋等內(nèi)容慎璧。
說明
推薦采用以下格式稳其,若工程已經(jīng)存在模板,與其他文件保持一致即可炸卑。
法律公告和作者信息
每個(gè)文件都應(yīng)該包含許可證引用. 為項(xiàng)目選擇合適的許可證版本.(比如, Apache 2.0, BSD, LGPL, GPL)
如果你對(duì)原始作者的文件做了重大修改, 請(qǐng)考慮刪除原作者信息.
文件內(nèi)容
如果一個(gè) .h 文件聲明了多個(gè)概念, 則文件注釋應(yīng)當(dāng)對(duì)文件的內(nèi)容做一個(gè)大致的說明, 同時(shí)說明各概念之間的聯(lián)系. 一個(gè)一到兩行的文件注釋就足夠了, 對(duì)于每個(gè)概念的詳細(xì)文檔應(yīng)當(dāng)放在各個(gè)概念中, 而不是文件注釋中.
/*
********************************************************************************
* Copyright (C),1975-2020, xxxxxx., Ltd.
* File Name : example.cpp
* Author :
* Version : V1.0
* Description: General driver template, if wanting to use it, you can use
* global case matching to replace DRIEVER_CASE and driver_case
* with your custom driver name.
* Journal : 2020-05-09 init v1.0 by xxxx
* Others : ********************************************************************************
*/
3.3 類注釋
總述
每個(gè)類的定義都要附帶一份注釋,除非它的功能相當(dāng)明顯煤傍。
說明
類注釋應(yīng)當(dāng)為讀者理解如何使用與何時(shí)使用類提供足夠的信息, 同時(shí)應(yīng)當(dāng)提醒讀者在正確使用此類時(shí)應(yīng)當(dāng)考慮的因素. 如果類有任何同步前提, 請(qǐng)用文檔說明. 如果該類的實(shí)例可被多線程訪問, 要特別注意文檔說明多線程環(huán)境下相關(guān)的規(guī)則和常量使用.
如果你想用一小段代碼演示這個(gè)類的基本用法或通常用法, 放在類注釋里也非常合適.
如果類的聲明和定義分開了(例如分別放在了.h和.cpp文件中), 此時(shí),描述類用法的注釋應(yīng)當(dāng)和接口定義放在一起, 描述類的操作和實(shí)現(xiàn)的注釋應(yīng)當(dāng)和實(shí)現(xiàn)放在一起.
3.4 函數(shù)注釋
總述
函數(shù)聲明處的注釋描述函數(shù)功能; 定義處的注釋描述函數(shù)實(shí)現(xiàn).
說明
函數(shù)聲明
基本上每個(gè)函數(shù)聲明處前都應(yīng)當(dāng)加上注釋, 描述函數(shù)的功能和用途. 只有在函數(shù)的功能簡(jiǎn)單而明顯時(shí)才能省略這些注釋(例如, 簡(jiǎn)單的取值和設(shè)值函數(shù)). 注釋使用敘述式 (“Opens the file”) 而非指令式 (“Open the file”); 注釋只是為了描述函數(shù), 而不是命令函數(shù)做什么. 通常, 注釋不會(huì)描述函數(shù)如何工作. 那是函數(shù)定義部分的事情.
經(jīng)常被調(diào)用的函數(shù)聲明處注釋內(nèi)容:
/*
* @description : Open the equipment
* @param - inode : transmit inode
* @param - filp : The device file, the file descriptor
* @return : 0: success; other: fail
*/
static int LedOpen(struct inode *inode, struct file *filp)
{
if (!atomic_dec_and_test(&gpioled.lock)) {
atomic_inc(&gpioled.lock);
return -EBUSY;
}
filp->private_data = &gpioled;
return 0;
}
但也要避免啰嗦盖文,或者對(duì)顯而易見的內(nèi)容進(jìn)行說明. 下面的注釋就沒有必要加上 “否則返回 false”, 因?yàn)橐呀?jīng)暗含其中了:
// Returns true if the table cannot hold any more entries.
bool IsTableFull();
注釋函數(shù)重載時(shí), 注釋的重點(diǎn)應(yīng)該是函數(shù)中被重載的部分, 而不是簡(jiǎn)單的重復(fù)被重載的函數(shù)的注釋. 多數(shù)情況下, 函數(shù)重載不需要額外的文檔, 因此也沒有必要加上注釋.
注釋構(gòu)造/析構(gòu)函數(shù)時(shí), 切記讀代碼的人知道構(gòu)造/析構(gòu)函數(shù)的功能, 所以 “銷毀這一對(duì)象” 這樣的注釋是沒有意義的. 你應(yīng)當(dāng)注明的是注明構(gòu)造函數(shù)對(duì)參數(shù)做了什么 (例如, 是否取得指針?biāo)袡?quán)) 以及析構(gòu)函數(shù)清理了什么. 如果都是些無關(guān)緊要的內(nèi)容, 直接省掉注釋. 析構(gòu)函數(shù)前沒有注釋是很正常的.
函數(shù)定義****
如果函數(shù)的實(shí)現(xiàn)過程中用到了很巧妙的方式, 那么在函數(shù)定義處應(yīng)當(dāng)加上解釋性的注釋. 例如, 你所使用的編程技巧, 實(shí)現(xiàn)的大致步驟, 或解釋如此實(shí)現(xiàn)的理由. 舉個(gè)例子, 你可以說明為什么函數(shù)的前半部分要加鎖而后半部分不需要.
不要從.h文件或其他地方的函數(shù)聲明處直接復(fù)制注釋. 簡(jiǎn)要重述函數(shù)功能是可以的, 但注釋重點(diǎn)要放在如何實(shí)現(xiàn)上.
3.5 變量注釋
總述
通常變量名本身足以很好說明變量用途。某些情況下蚯姆,也需要額外注釋說明五续。
說明
類數(shù)據(jù)成員
每個(gè)類數(shù)據(jù)成員 (也叫實(shí)例變量或成員變量) 都應(yīng)該用注釋說明用途. 如果有非變量的參數(shù)(例如特殊值, 數(shù)據(jù)成員之間的關(guān)系, 生命周期等)不能夠用類型與變量名明確表達(dá), 則應(yīng)當(dāng)加上注釋. 然而, 如果變量類型與變量名已經(jīng)足以描述一個(gè)變量, 那么就不再需要加上注釋.
特別地, 如果變量可以接受 NULL 或 -1 等警戒值, 須加以說明. 比如:
private:
/* Used to bounds-check table accesses. -1 means
that we don't yet know how many entries the table has. */
int m_num_total_entries;
全局變量
和數(shù)據(jù)成員一樣, 所有全局變量也要注釋說明含義及用途, 以及作為全局變量的原因. 比如:
// The total number of tests cases that we run through in this regression test.
const int g_num_testcases = 6;
3.6 實(shí)現(xiàn)注釋
總述
對(duì)于代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以注釋。
說明
代碼前注釋
/* Divide result by two, taking into account that x
contains the carry from the add. */
for (int i = 0; i < result->size(); i++) {
x = (x << 8) + (*result)[i];
(*result)[i] = x >> 1;
x &= 1;
}
行注釋
比較隱晦的地方要在行尾加入注釋龄恋。在行尾空兩格進(jìn)行注釋疙驾。 比如:
// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
return; // Error already logged.
3.7 標(biāo)點(diǎn),拼音和語(yǔ)法
總述
注意標(biāo)點(diǎn), 拼寫和語(yǔ)法; 寫的好的注釋比差的要易讀的多郭毕。
說明
注釋的通常寫法是包含正確大小寫和結(jié)尾句號(hào)的完整敘述性語(yǔ)句. 大多數(shù)情況下, 完整的句子比句子片段可讀性更高. 短一點(diǎn)的注釋, 比如代碼行尾注釋, 可以隨意點(diǎn), 但依然要注意風(fēng)格的一致性它碎。
雖然被別人指出該用分號(hào)時(shí)卻用了逗號(hào)多少有些尷尬, 但清晰易讀的代碼還是很重要的. 正確的標(biāo)點(diǎn), 拼寫和語(yǔ)法對(duì)此會(huì)有很大幫助。
3.8 TODO注釋
總述
對(duì)那些臨時(shí)的, 短期的解決方案, 或已經(jīng)夠好但仍不完美的代碼使用 TODO 注釋.
TODO 注釋要使用全大寫的字符串 TODO, 在隨后的圓括號(hào)里寫上你的名字, 郵件地址, bug ID, 或其它身份標(biāo)識(shí)和與這一 TODO 相關(guān)的 issue. 主要目的是讓添加注釋的人 (也是可以請(qǐng)求提供更多細(xì)節(jié)的人) 可根據(jù)規(guī)范的 TODO 格式進(jìn)行查找. 添加 TODO 注釋并不意味著你要自己來修正, 因此當(dāng)你加上帶有姓名的 TODO 時(shí), 一般都是寫上自己的名字显押。
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
如果加 TODO 是為了在 “將來某一天做某事”, 可以附上一個(gè)非常明確的時(shí)間 “Fix by November 2005”), 或者一個(gè)明確的事項(xiàng) (“Remove this code when all clients can handle XML responses.”)扳肛。
4 格式
每個(gè)人都可能有自己的代碼風(fēng)格和格式, 但如果一個(gè)項(xiàng)目中的所有人都遵循同一風(fēng)格的話, 這個(gè)項(xiàng)目就能更順利地進(jìn)行. 每個(gè)人未必能同意下述的每一處格式規(guī)則, 而且其中的不少規(guī)則需要一定時(shí)間的適應(yīng), 但整個(gè)項(xiàng)目服從統(tǒng)一的編程風(fēng)格是很重要的, 只有這樣才能讓所有人輕松地閱讀和理解代碼。
4.1 行長(zhǎng)度
總述
每行長(zhǎng)度字符數(shù)不超過80乘碑。
盡管這條規(guī)則具有爭(zhēng)議挖息,Linux源碼的風(fēng)格也是遵照這一規(guī)則,因此一致性更重要兽肤。
優(yōu)點(diǎn)
提倡該原則的人認(rèn)為強(qiáng)迫他們調(diào)整編輯器窗口大小是很野蠻的行為. 很多人同時(shí)并排開幾個(gè)代碼窗口, 根本沒有多余的空間拉伸窗口. 大家都把窗口最大尺寸加以限定, 并且 80 列寬是傳統(tǒng)標(biāo)準(zhǔn)套腹。
缺點(diǎn)
反對(duì)該原則的人則認(rèn)為更寬的代碼行更易閱讀. 80 列的限制是上個(gè)世紀(jì) 60 年代的大型機(jī)的古板缺陷; 現(xiàn)代設(shè)備具有更寬的顯示屏, 可以很輕松地顯示更多代碼绪抛。
結(jié)論
如果無法在不傷害易讀性的條件下進(jìn)行斷行, 那么注釋行可以超過 80 個(gè)字符, 這樣可以方便復(fù)制粘貼. 例如, 帶有命令示例或 URL 的行可以超過 80 個(gè)字符。
包含長(zhǎng)路徑的 #include 語(yǔ)句可以超出80列电禀。
4.2 非ASCII字符
總述
盡量不使用非 ASCII 字符, 使用時(shí)必須使用 UTF-8 編碼幢码。
4.3 空格還是制表位
總述
只使用空格,每次縮進(jìn)4個(gè)空格鞭呕。
說明
我們使用空格縮進(jìn). 不要在代碼中使用制表符蛤育。應(yīng)該設(shè)置編輯器將制表符轉(zhuǎn)為空格。Vim中Makefile需要使用制表位葫松,Ctrl+V Tab即可出現(xiàn)制位表瓦糕。
4.4 函數(shù)聲明與定義
總述
返回類型和函數(shù)名在同一行,參數(shù)也盡量放在同一行腋么,如果放不下就對(duì)形參進(jìn)行分行咕娄,分行方式與函數(shù)調(diào)用一致。
說明
函數(shù)看上去像這樣:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
如果同一行文本太多, 放不下所有參數(shù):
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3) {
DoSomething();
...
}
甚至連第一個(gè)參數(shù)都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); //4space indent
...
}
注意以下幾點(diǎn):
- 使用好的參數(shù)名.
- 只有在參數(shù)未被使用或者其用途非常明顯時(shí), 才能省略參數(shù)名.
- 如果返回類型和函數(shù)名在一行放不下, 分行.
- 如果返回類型與函數(shù)聲明或定義分行了, 不要縮進(jìn).
- 左圓括號(hào)總是和函數(shù)名在同一行.
- 函數(shù)名和左圓括號(hào)間永遠(yuǎn)沒有空格.
- 圓括號(hào)與參數(shù)間沒有空格.
- 左大括號(hào)總在最后一個(gè)參數(shù)同一行的末尾處, 不另起新行.
- 右大括號(hào)總是單獨(dú)位于函數(shù)最后一行, 或者與左大括號(hào)同一行.
- 右圓括號(hào)和左大括號(hào)間總是有一個(gè)空格.
- 所有形參應(yīng)盡可能對(duì)齊.
- 缺省縮進(jìn)為 4 個(gè)空格.
- 換行后的參數(shù)保持 4 個(gè)空格的縮進(jìn).
4.5 條件語(yǔ)句
總述
傾向于不在圓括號(hào)內(nèi)使用空格. 關(guān)鍵字 if 和 else 另起一行.
說明
- if 與圓括號(hào)()與{} 都需要空格隔開
- if (condition) { // 好 - if 和 { 都與空格緊鄰.
- if-else后面無論包含幾行代碼珊擂,都必須緊跟{}圣勒。
4.6 循環(huán)和開關(guān)選擇語(yǔ)句
總述
循環(huán)語(yǔ)句使用{}分段。盡管很多風(fēng)格選擇switch使用{}用來表明case之間不是連在一起的摧扇,但是這里采用linux內(nèi)核風(fēng)格圣贸,不推薦case使用{}包含分支,且case位置要與switch對(duì)齊扛稽。
說明
如果有不滿足 case 條件的枚舉值, switch 應(yīng)該總是包含一個(gè) default 匹配 (如果有輸入值沒有 case 去處理, 編譯器將給出 warning). 如果 default 應(yīng)該永遠(yuǎn)執(zhí)行不到, 簡(jiǎn)單的加條 assert:
switch (var) {
case 0: // 與switch對(duì)齊
... // 4 空格縮進(jìn)
break;
case 1:
...
break;
default:
assert(false);
break吁峻;
}
4.7 指針和引用表達(dá)式
總述
句點(diǎn)或箭頭前后不要有空格. 指針/地址操作符 (*, &) 之后不能有空格.
說明
下面是指針和引用表達(dá)式的正確使用范例:
x = *p;
p = &x;
x = r.y;
x = r->y;
注意:
- 在訪問成員時(shí), 句點(diǎn)或箭頭前后沒有空格.
- 指針操作符 * 或 & 后沒有空格.
- 在聲明指針變量或參數(shù)時(shí), 星號(hào)與類型或變量名緊挨都可以:
// 好, 空格前置.
char *c;
const string &str;
// 好, 空格后置.
char* c;
const string& str;
int x, *y; // 不允許 - 在多重聲明中不能使用 & 或 *
char * c; // 差 - * 兩邊都有空格
const string & str; // 差 - & 兩邊都有空格.
在單個(gè)文件內(nèi)要保持風(fēng)格一致, 所以, 如果是修改現(xiàn)有文件, 要遵照該文件的風(fēng)格.
4.8 布爾表達(dá)式
總述
如果一個(gè)布爾表達(dá)式超過80行, 斷行方式要統(tǒng)一一下.
說明
下例中, 邏輯與 (&&) 操作符總位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
4.9 預(yù)處理指令
總述
預(yù)處理指令不要縮進(jìn), 從行首開始.
說明
即使預(yù)處理指令位于縮進(jìn)代碼塊中, 指令也應(yīng)從行首開始.
// 好 - 指令從行首開始
if (lopsided_score) {
#if DISASTER_PENDING // 正確 - 從行首開始
DropEverything();
# if NOTIFY // 非必要 - # 后跟空格
NotifyClient();
# endif
#endif
BackToNormal();
}
// 差 - 指令縮進(jìn)
if (lopsided_score) {
#if DISASTER_PENDING // 差 - "#if" 應(yīng)該放在行開頭
DropEverything();
#endif // 差 - "#endif" 不要縮進(jìn)
BackToNormal();
}
4.10 類格式
總述
訪問控制塊的聲明依次序是 public:, protected:, private:, 每個(gè)都縮進(jìn) 2 個(gè)空格。
說明
類聲明的基本格式如下:
class MyClass : public OtherClass {
public: // 注意有一個(gè)空格的縮進(jìn)
MyClass(); // 標(biāo)準(zhǔn)的兩空格縮進(jìn)
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int m_some_var;
int m_some_other_var;
};
注意事項(xiàng):
- 所有基類名應(yīng)在 80 列限制下盡量與子類名放在同一行.
- 關(guān)鍵詞 public:, protected:, private: 要縮進(jìn) 2 個(gè)空格.
- 除第一個(gè)關(guān)鍵詞 (一般是 public) 外, 其他關(guān)鍵詞前要空一行. 如果類比較小的話也可以不空.
- 這些關(guān)鍵詞后不要保留空行.
- public 放在最前面, 然后是 protected, 最后是 private.
4.11 水平留白
總述
水平留白的使用根據(jù)在代碼中的位置決定. 永遠(yuǎn)不要在行尾添加沒意義的留白在张。
說明
通用
void f(bool b) { // 左大括號(hào)前總是有空格.
...
int i = 0; // 分號(hào)前不加空格.
// 列表初始化中大括號(hào)內(nèi)的空格是可選的.
// 如果加了空格, 那么兩邊都要加上.
int x[] = { 0 };
int x[] = {0};
}
// 繼承與初始化列表中的冒號(hào)前后恒有空格.
class Foo : public Bar {
public:
/* 對(duì)于單行函數(shù)的實(shí)現(xiàn), 在大括號(hào)內(nèi)加上空格
然后是函數(shù)實(shí)現(xiàn) */
Foo(int b) : Bar(), baz_(b) {} // 大括號(hào)里面是空的話, 不加空格.
void Reset() { baz_ = 0; } // 用括號(hào)把大括號(hào)與實(shí)現(xiàn)分開.
...
}
添加冗余的留白會(huì)給其他人編輯時(shí)造成額外負(fù)擔(dān). 因此, 行尾不要留空格. 如果確定一行代碼已經(jīng)修改完畢, 將多余的空格去掉; 或者在專門清理空格時(shí)去掉用含。
循環(huán)和條件語(yǔ)句
if (b) { // if 條件語(yǔ)句和循環(huán)語(yǔ)句關(guān)鍵字后均有空格.
...
} else { // else 前后有空格.
}while (test) {} // 圓括號(hào)內(nèi)部不緊鄰空格.
switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) { // 循環(huán)和條件語(yǔ)句的圓括號(hào)里可以與空格緊鄰.
if ( test ) { // 圓括號(hào), 但這很少見. 總之要一致.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) { // 循環(huán)里內(nèi) ; 后恒有空格, ; 前可以加個(gè)空格.
switch (i) {
case 1: // switch case 的冒號(hào)前無空格.
...
case 2: break; // 如果冒號(hào)有代碼, 加個(gè)空格.
}
}
}
操作符
// 賦值運(yùn)算符前后總是有空格.
x = 0;
// 其它二元操作符也前后恒有空格, 不過對(duì)于表達(dá)式的子式可以不加空格.
// 圓括號(hào)內(nèi)部沒有緊鄰空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 在參數(shù)和一元操作符之間不加空格.
x = -5;
++x;
if (x && !y) {
...
}
模板和轉(zhuǎn)換
// 尖括號(hào)(< and >) 不與空格緊鄰, < 前沒有空格, > 和 ( 之間也沒有.
vector<string> x;
y = static_cast<char*>(x);
// 在類型與指針操作符之間留空格也可以, 但要保持一致.
vector<char *> x;
類數(shù)據(jù)成員與函數(shù)成員
總述
一般情況下,在類中函數(shù)成員與數(shù)據(jù)成員之間要一行留白帮匾,便于查看啄骇。
說明
class Student {
public:
GetStuName();
ShowScore();
String m_name;
int m_score;
}
4.12 垂直留白
總述
垂直留白越少越好.
說明
這不僅僅是規(guī)則而是原則問題了: 不在萬不得已, 不要使用空行. 尤其是: 兩個(gè)函數(shù)定義之間的空行不要超過 2 行, 函數(shù)體首尾不要留空行, 函數(shù)體中也不要隨意添加空行.
基本原則是: 同一屏可以顯示的代碼越多, 越容易理解程序的控制流. 當(dāng)然, 過于密集的代碼塊和過于疏松的代碼塊同樣難看, 這取決于你的判斷. 但通常是垂直留白越少越好.
下面的規(guī)則可以讓加入的空行更有效:
- 函數(shù)體內(nèi)開頭或結(jié)尾的空行可讀性微乎其微.
- 在多重 if-else 塊里加空行或許有點(diǎn)可讀性.