天天看點

C++打怪 之 程式設計風格指南 V1.0序1 函數2 命名約定3 注釋4 格式參考

C++用法很多,包容性也比較強。一個C++的工程可能包含了各種各樣沒見過的用法。本篇内容主要是參照谷歌C++标準規範,結合自身實際工作 及經驗,整理一份适合平時C++開發的規則,規範自身C++程式設計規範。詳細内容可參考《Google C++風格指南》。

1 函數

1.1 參數順序

總述

函數的參數順序為: 輸入參數在先, 後跟輸出參數.

說明

C/C++ 中的函數參數或者是函數的輸入, 或者是函數的輸出, 或兼而有之. 輸入參數通常是值參或 const引用, 輸出參數或輸入/輸出參數則一般為非 const 指針. 在排列參數順序時, 将所有的輸入參數置于輸出參數之前. 特别要注意, 在加入新參數時不要因為它們是新參數就置于參數清單最後, 而是仍然要按照前述的規則, 即将新的輸入參數也置于輸出參數之前.

這并非一個硬性規定. 輸入/輸出參數 (通常是類或結構體) 讓這個問題變得複雜. 并且, 有時候為了其他函數保持一緻, 你可能不得不有所變通.

1.2 編寫簡短函數

總述

我們傾向于編寫簡短, 凝練的函數.

說明

長函數有時是合理的, 是以并不硬性限制函數的長度. 如果函數超過 40 行, 可以思索一下能不能在不影響程式結構的前提下對其進行分割.

即使一個長函數現在工作的非常好, 一旦有人對其修改, 有可能出現新的問題, 甚至導緻難以發現的 bug. 使函數盡量簡短, 以便于他人閱讀和修改代碼.

在處理代碼時, 你可能會發現複雜的長函數. 不要害怕修改現有代碼: 如果證明這些代碼使用 / 調試起來很困難, 或者你隻需要使用其中的一小段代碼, 考慮将其分割為更加簡短并易于管理的若幹函數.

1.3 引用參數

總述

所有按引用傳遞的參數必須加上 const.

定義

在 C 語言中, 如果函數需要修改變量的值, 參數必須為指針, 如 int foo(int *pval). 在 C++ 中, 函數還可以聲明為引用參數: int foo(int &val).

優點

定義引用參數可以防止出現 (*pval)++ 這樣醜陋的代碼. 引用參數對于拷貝構造函數這樣的應用也是必需的. 同時也更明确地不接受空指針。

缺點

容易引起誤解, 因為引用在文法上是值變量卻擁有指針的語義。

結論

函數參數清單中, 所有引用參數都必須是 const:

void Foo(const string &in, string *out);
           

複制

1.4 函數重載

總述

若要使用函數重載, 則必須能讓讀者一看調用點就胸有成竹, 而不用花心思猜測調用的重載函數到底是哪一種. 這一規則也适用于構造函數。

定義

你可以編寫一個參數類型為 const string& 的函數, 然後用另一個參數類型為 const char* 的函數對其進行重載:

class MyClass {
  public:
    void Analyze(const string &text);
    void Analyze(const char *text, size_t textlen);
};
           

複制

優點

通過重載參數不同的同名函數, 可以令代碼更加直覺. 模闆化代碼需要重載, 這同時也能為使用者帶來便利。

缺點

如果函數單靠不同的參數類型而重載 (acgtyrant 注:這意味着參數數量不變), 讀者就得十分熟悉 C++ 五花八門的比對規則, 以了解比對過程具體到底如何. 另外, 如果派生類隻重載了某個函數的部分變體, 繼承語義就容易令人困惑.

結論

如果打算重載一個函數, 可以試試改在函數名裡加上參數資訊. 例如, 用 AppendString() 和 AppendInt() 等, 而不是一口氣重載多個 Append(). 如果重載函數的目的是為了支援不同數量的同一類型參數, 則優先考慮使用 std::vector 以便使用者可以用 清單初始化 指定參數。

1.5 函數傳回類型後置文法

總述

隻有在正常寫法 (傳回類型前置) 不便于書寫或不便于閱讀時使用傳回類型後置文法.

定義

C++ 現在允許兩種不同的函數聲明方式. 以往的寫法是将傳回類型置于函數名之前. 例如:

int foo(int x);
           

複制

C++11 引入了這一新的形式. 現在可以在函數名前使用 auto 關鍵字, 在參數清單之後後置傳回類型. 例如:

auto foo(int x) -> int;
           

複制

後置傳回類型為函數作用域. 對于像 int 這樣簡單的類型, 兩種寫法沒有差別. 但對于複雜的情況, 例如類域中的類型聲明或者以函數參數的形式書寫的類型, 寫法的不同會造成差別.

優點

後置傳回類型是顯式地指定 Lambda 表達式 的傳回值的唯一方式. 某些情況下, 編譯器可以自動推導出 Lambda 表達式的傳回類型, 但并不是在所有的情況下都能實作. 即使編譯器能夠自動推導, 顯式地指定傳回類型也能讓讀者更明了.

有時在已經出現了的函數參數清單之後指定傳回類型, 能夠讓書寫更簡單, 也更易讀, 尤其是在傳回類型依賴于模闆參數時. 例如:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);
           

複制

對比下面的例子:

template <class T, class U> decltype(declval<T&>() + declval<U&>()) add(T t, U u);
           

複制

缺點

後置傳回類型相對來說是非常新的文法, 而且在 C 和 Java 中都沒有相似的寫法, 是以可能對讀者來說比較陌生.

在已有的代碼中有大量的函數聲明, 你不可能把它們都用新的文法重寫一遍. 是以實際的做法隻能是使用舊的文法或者新舊混用. 在這種情況下, 隻使用一種版本是相對來說更規整的形式.

結論

在大部分情況下, 應當繼續使用以往的函數聲明寫法, 即将傳回類型置于函數名前. 隻有在必需的時候 (如 Lambda 表達式) 或者使用後置文法能夠簡化書寫并且提高易讀性的時候才使用新的傳回類型後置文法. 但是後一種情況一般來說是很少見的, 大部分時候都出現在相當複雜的模闆代碼中, 而多數情況下不鼓勵寫這樣複雜的模闆代碼.

2 命名約定

2.1 通用命名規則

盡可能使用描述性的命名,不要用隻有項目開發者才能了解的縮寫,也不能通過砍掉字母來縮寫單詞(除非是程式員都熟悉的縮寫)。

2.2 檔案命名

總述

檔案名要全部小寫,多個單詞用下劃線_或者‘-’連接配接。推薦使用“_”。

說明

可接受的檔案名命名示例:

my_useful_class.cpp
my-useful-class.cpp
           

複制

不要使用已經存在于 /usr/include 下的檔案名 (Yang.Y 注: 即編譯器搜尋系統頭檔案的路徑), 如 db.h.

通常應盡量讓檔案名更加明确. http_server_logs.h就比 logs.h 要好. 定義類時檔案名一般成對出現, 如 foo_bar.h 和 foo_bar.cc, 對應于類 FooBar.

内聯函數必須放在 .h 檔案中. 如果内聯函數比較短, 就直接放在 .h 中.

2.3 類型命名

總述

類型名稱的每個單詞首字母均大寫, 不包含下劃線:MyExcitingClass, MyExcitingEnum.

說明

所有類型命名 —— 類, 結構體, 類型定義 (typedef), 枚舉, 類型模闆參數 —— 均使用相同約定, 即以大寫字母開始, 每個單詞首字母均大寫, 不包含下劃線. 例如:

// 類和結構體
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...

// 類型**定義**
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// using 别名
using PropertiesMap = hash_map<UrlTableProperties *, string>;

// 枚舉
enum UrlTableErrors { ...
           

複制

2.4 變量命名

總述

變量 (包括函數參數) 和資料成員名一律小寫, 單詞之間用下劃線連接配接. 類的成員變量以m_開始, 但結構體的就不用, 如: a_local_variable, a_struct_data_member, m_class_data_member.

變量辨別符與對應的類型辨別符之間的差別應避免僅在于用小寫字母寫的初始字母不同情況。eg:Path path;是不可取的

說明

普通變量命名

舉例:

string table_name;  // 好 - 用下劃線.string tablename;   // 好 - 全小寫.

string tableName;  // 差 - 混合大小寫
           

複制

類資料成員

不管是靜态還是非靜态的,類資料成員都可以和普通變量一樣,但要加m或者m_字首。

若是m字首則m後緊跟大寫字母;若使用m_,後接小寫字母。

m_用法:後接下劃線和全小寫

class TableInfo {
  ...

  private:
    string m_table_name;  // 好 - 後加下劃線.
    string m_tablename;   // 好.
    static Pool<TableInfo>* m_pool;  // 好.
};
           

複制

m用法:後接首字母大寫的獨立單詞

class TableInfo {
  ...
  private:
    string mTableName;  // 好 - 後加下劃線.
    string mTableName;   // 好.
    static Pool<TableInfo>* mPool;  // 好.

};
           

複制

推薦使用第一種!!

結構體變量

不管是靜态的還是非靜态的, 結構體資料成員都可以和普通變量一樣, 不用像類成員那樣接下劃線。

struct UrlTableProperties {
    string name;
    int num_entries;
    static Pool<UrlTableProperties>* pool;
};
           

複制

全局變量

總述

一般情況下禁止使用全局變量,非不得已情況下采用g_字首,其他格式與普通變量相同。

說明

凡是需要采用全局變量extern,盡量都優化為get,set修改内部static靜态變量。

int g_value;

char g_name[] = {};
           

複制

**注:**全局變量和靜态全局變量均采用此命名格式。

指針變量

總述

采用指針變量時要格外小心,盡量在聲明時就初始化。避免程式中使用未初始化的野指針,進而導緻程式崩潰。

說明

指針變量采用“駝峰”命名規則,即小寫p字首、大小寫混合、單詞首字母大寫。

char name[] = “C++ Style”;
char *pStr, *pName = name[0];
pStr = name;
           

複制

2.5 常量命名

在程式代碼中,所有固定的數值(可能除了- 1,0和1)都要用常量替換,也就是說,不使用“神奇的數字”(這些數字後來都不知道這個值代表什麼)常量不允許使用小寫字母,是以名稱部分之間的下劃線在這裡是允許的。

const double PI = 3.14
const int MAX_SIZE = 100;
           

複制

2.6 函數命名

總述

正常函數使用大小寫混合, 取值和設值函數則要求與變量名比對:

MyExctingFunction();
MyExcitingMethod();
my_exciting_member_variable();
set_my_exciting_member_variable();
           

複制

說明

推薦使用,函數名每個單詞首字母都需要大寫,沒有下劃線。對于首字母縮寫的單詞,更傾向于将它們視作一個單詞進行首字母大寫 (例如, 寫作StartRpc()而非StartRPC())。

AddTableEntry();
DeleteUrl();
OpenFileOrDie();
           

複制

取值和設值函數的命名與變量一緻. 一般來說它們的名稱與實際的成員變量對應, 但并不強制要求. 例如 int count()與void SetCount(int count)。

2.7 命名空間命名

2.8 枚舉命名

總述

枚舉的命名應當于常量和宏一緻,以大寫E字母開頭,多個單詞用下劃線_連接配接。ESIZE_SEARCH。

說明

由于枚舉的功能與宏功能類似,故規定枚舉命名規則與宏命名一緻。為避免與宏命名沖突,規定枚舉命名以大寫字母E作為字首,使得枚舉命名更加清晰。

enum EFormatStateExtPvr    
{
    FORMAT_STATE_PROGRESS = 0x1,
    FORMAT_STATE_NOTSET= 0xF
};
           

複制

2.9 宏命名

總述

谷歌規則中推薦使用内聯函數、枚舉和常量代替宏的使用。

說明

宏的定義規則與枚舉一樣,隻不過不需要任何字母字首,大寫以下劃線_連接配接。

#define PI 3.14
#define MAX_SIZE 1024
#define ROUND(x) ...
           

複制

宏定義中的參數,可以使用小寫。

3 注釋

注釋的重要性不亞于代碼實作,好的注釋能夠讓代碼可讀性更強。優秀的注釋能減輕開發人員自身的負擔,提高團隊開發效率。當然也要注意:注釋固然重要,但最好的注釋就是代碼本身,有意義的類型名和變量名,要遠勝于用注釋解釋含糊不清的名字。

3.1 注釋風格

總述

使用// 或 /* */,都是允許的,隻要統一即可。内容語言建議使用英文。

說明

本文推薦單行注釋采用//,多行注釋采用/* */形式。若工程中已經存在注釋風格,需要與目前工程保持一緻即可。

3.2 檔案注釋

總述

在每一個檔案的開頭加入版權公告。

檔案注釋應包括版權、檔案名、作者、版本、描述、日志、注釋等内容。

說明

推薦采用以下格式,若工程已經存在模闆,與其他檔案保持一緻即可。

法律公告和作者資訊

每個檔案都應該包含許可證引用. 為項目選擇合适的許可證版本.(比如, Apache 2.0, BSD, LGPL, GPL)

如果你對原始作者的檔案做了重大修改, 請考慮删除原作者資訊.

檔案内容

如果一個 .h 檔案聲明了多個概念, 則檔案注釋應當對檔案的内容做一個大緻的說明, 同時說明各概念之間的聯系. 一個一到兩行的檔案注釋就足夠了, 對于每個概念的詳細文檔應當放在各個概念中, 而不是檔案注釋中.

/*
********************************************************************************
* 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 類注釋

總述

每個類的定義都要附帶一份注釋,除非它的功能相當明顯。

說明

類注釋應當為讀者了解如何使用與何時使用類提供足夠的資訊, 同時應當提醒讀者在正确使用此類時應當考慮的因素. 如果類有任何同步前提, 請用文檔說明. 如果該類的執行個體可被多線程通路, 要特别注意文檔說明多線程環境下相關的規則和常量使用.

如果你想用一小段代碼示範這個類的基本用法或通常用法, 放在類注釋裡也非常合适.

如果類的聲明和定義分開了(例如分别放在了.h和.cpp檔案中), 此時,描述類用法的注釋應當和接口定義放在一起, 描述類的操作和實作的注釋應當和實作放在一起.

3.4 函數注釋

總述

函數聲明處的注釋描述函數功能; 定義處的注釋描述函數實作.

說明

函數聲明

基本上每個函數聲明處前都應當加上注釋, 描述函數的功能和用途. 隻有在函數的功能簡單而明顯時才能省略這些注釋(例如, 簡單的取值和設值函數). 注釋使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”); 注釋隻是為了描述函數, 而不是指令函數做什麼. 通常, 注釋不會描述函數如何工作. 那是函數定義部分的事情.

經常被調用的函數聲明處注釋内容:

/*
* @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;
 }
           

複制

但也要避免啰嗦,或者對顯而易見的内容進行說明. 下面的注釋就沒有必要加上 “否則傳回 false”, 因為已經暗含其中了:

// Returns true if the table cannot hold any more entries.
bool IsTableFull();
           

複制

注釋函數重載時, 注釋的重點應該是函數中被重載的部分, 而不是簡單的重複被重載的函數的注釋. 多數情況下, 函數重載不需要額外的文檔, 是以也沒有必要加上注釋.

注釋構造/析構函數時, 切記讀代碼的人知道構造/析構函數的功能, 是以 “銷毀這一對象” 這樣的注釋是沒有意義的. 你應當注明的是注明構造函數對參數做了什麼 (例如, 是否取得指針所有權) 以及析構函數清理了什麼. 如果都是些無關緊要的内容, 直接省掉注釋. 析構函數前沒有注釋是很正常的.

函數定義****

如果函數的實作過程中用到了很巧妙的方式, 那麼在函數定義處應當加上解釋性的注釋. 例如, 你所使用的程式設計技巧, 實作的大緻步驟, 或解釋如此實作的理由. 舉個例子, 你可以說明為什麼函數的前半部分要加鎖而後半部分不需要.

不要從.h檔案或其他地方的函數聲明處直接複制注釋. 簡要重述函數功能是可以的, 但注釋重點要放在如何實作上.

3.5 變量注釋

總述

通常變量名本身足以很好說明變量用途。某些情況下,也需要額外注釋說明。

說明

類資料成員

每個類資料成員 (也叫執行個體變量或成員變量) 都應該用注釋說明用途. 如果有非變量的參數(例如特殊值, 資料成員之間的關系, 生命周期等)不能夠用類型與變量名明确表達, 則應當加上注釋. 然而, 如果變量類型與變量名已經足以描述一個變量, 那麼就不再需要加上注釋.

特别地, 如果變量可以接受 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;
           

複制

全局變量

和資料成員一樣, 所有全局變量也要注釋說明含義及用途, 以及作為全局變量的原因. 比如:

// The total number of tests cases that we run through in this regression test.
const int g_num_testcases = 6;
           

複制

3.6 實作注釋

總述

對于代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以注釋。

說明

代碼前注釋

/* 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;
    }
           

複制

行注釋

比較隐晦的地方要在行尾加入注釋。在行尾空兩格進行注釋。 比如:

// 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 标點,拼音和文法

總述

注意标點, 拼寫和文法; 寫的好的注釋比差的要易讀的多。

說明

注釋的通常寫法是包含正确大小寫和結尾句号的完整叙述性語句. 大多數情況下, 完整的句子比句子片段可讀性更高. 短一點的注釋, 比如代碼行尾注釋, 可以随意點, 但依然要注意風格的一緻性。

雖然被别人指出該用分号時卻用了逗号多少有些尴尬, 但清晰易讀的代碼還是很重要的. 正确的标點, 拼寫和文法對此會有很大幫助。

3.8 TODO注釋

總述

對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 注釋.

TODO 注釋要使用全大寫的字元串 TODO, 在随後的圓括号裡寫上你的名字, 郵件位址, bug ID, 或其它身份辨別和與這一 TODO 相關的 issue. 主要目的是讓添加注釋的人 (也是可以請求提供更多細節的人) 可根據規範的 TODO 格式進行查找. 添加 TODO 注釋并不意味着你要自己來修正, 是以當你加上帶有姓名的 TODO 時, 一般都是寫上自己的名字。

// TODO([email protected]): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
           

複制

如果加 TODO 是為了在 “将來某一天做某事”, 可以附上一個非常明确的時間 “Fix by November 2005”), 或者一個明确的事項 (“Remove this code when all clients can handle XML responses.”)。

4 格式

每個人都可能有自己的代碼風格和格式, 但如果一個項目中的所有人都遵循同一風格的話, 這個項目就能更順利地進行. 每個人未必能同意下述的每一處格式規則, 而且其中的不少規則需要一定時間的适應, 但整個項目服從統一的程式設計風格是很重要的, 隻有這樣才能讓所有人輕松地閱讀和了解代碼。

4.1 行長度

總述

每行長度字元數不超過80。

盡管這條規則具有争議,Linux源碼的風格也是遵照這一規則,是以一緻性更重要。

優點

提倡該原則的人認為強迫他們調整編輯器視窗大小是很野蠻的行為. 很多人同時并排開幾個代碼視窗, 根本沒有多餘的空間拉伸視窗. 大家都把視窗最大尺寸加以限定, 并且 80 列寬是傳統标準。

缺點

反對該原則的人則認為更寬的代碼行更易閱讀. 80 列的限制是上個世紀 60 年代的大型機的古闆缺陷; 現代裝置具有更寬的顯示屏, 可以很輕松地顯示更多代碼。

結論

如果無法在不傷害易讀性的條件下進行斷行, 那麼注釋行可以超過 80 個字元, 這樣可以友善複制粘貼. 例如, 帶有指令示例或 URL 的行可以超過 80 個字元。

包含長路徑的 #include 語句可以超出80列。

4.2 非ASCII字元

總述

盡量不使用非 ASCII 字元, 使用時必須使用 UTF-8 編碼。

4.3 空格還是制表位

總述

隻使用空格,每次縮進4個空格。

說明

我們使用空格縮進. 不要在代碼中使用制表符。應該設定編輯器将制表符轉為空格。Vim中Makefile需要使用制表位,Ctrl+V Tab即可出現制位表。

4.4 函數聲明與定義

總述

傳回類型和函數名在同一行,參數也盡量放在同一行,如果放不下就對形參進行分行,分行方式與函數調用一緻。

說明

函數看上去像這樣:

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
    DoSomething();
    ...
}
           

複制

如果同一行文本太多, 放不下所有參數:

ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                             Type par_name3) {
    DoSomething();
    ...
}
           

複制

甚至連第一個參數都放不下:

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) {

    DoSomething();  //4space indent
    ...
}
           

複制

注意以下幾點:

  • 使用好的參數名.
  • 隻有在參數未被使用或者其用途非常明顯時, 才能省略參數名.
  • 如果傳回類型和函數名在一行放不下, 分行.
  • 如果傳回類型與函數聲明或定義分行了, 不要縮進.
  • 左圓括号總是和函數名在同一行.
  • 函數名和左圓括号間永遠沒有空格.
  • 圓括号與參數間沒有空格.
  • 左大括号總在最後一個參數同一行的末尾處, 不另起新行.
  • 右大括号總是單獨位于函數最後一行, 或者與左大括号同一行.
  • 右圓括号和左大括号間總是有一個空格.
  • 所有形參應盡可能對齊.
  • 預設縮進為 4 個空格.
  • 換行後的參數保持 4 個空格的縮進.

4.5 條件語句

總述

傾向于不在圓括号内使用空格. 關鍵字 if 和 else 另起一行.

說明

  • if 與圓括号()與{} 都需要空格隔開
  • if (condition) { // 好 - if 和 { 都與空格緊鄰.
  • if-else後面無論包含幾行代碼,都必須緊跟{}。

4.6 循環和開關選擇語句

總述

循環語句使用{}分段。盡管很多風格選擇switch使用{}用來表明case之間不是連在一起的,但是這裡采用linux核心風格,不推薦case使用{}包含分支,且case位置要與switch對齊。

說明

如果有不滿足 case 條件的枚舉值, switch 應該總是包含一個 default 比對 (如果有輸入值沒有 case 去處理, 編譯器将給出 warning). 如果 default 應該永遠執行不到, 簡單的加條 assert:

switch (var) {
case 0:     // 與switch對齊
    ...      // 4 空格縮進
    break;
case 1:
    ...
    break;
default:
    assert(false);
    break;    
}
           

複制

4.7 指針和引用表達式

總述

句點或箭頭前後不要有空格. 指針/位址操作符 (*, &) 之後不能有空格.

說明

下面是指針和引用表達式的正确使用範例:

x = *p;
p = &x;
x = r.y;
x = r->y;
           

複制

注意:

  • 在通路成員時, 句點或箭頭前後沒有空格.
  • 指針操作符 * 或 & 後沒有空格.
  • 在聲明指針變量或參數時, 星号與類型或變量名緊挨都可以:
// 好, 空格前置.
char *c;
const string &str;

// 好, 空格後置.
char* c;
const string& str;

int x, *y;  // 不允許 - 在多重聲明中不能使用 & 或 *
char * c;  // 差 - * 兩邊都有空格
const string & str;  // 差 - & 兩邊都有空格.
           

複制

在單個檔案内要保持風格一緻, 是以, 如果是修改現有檔案, 要遵照該檔案的風格.

4.8 布爾表達式

總述如果一個布爾表達式超過80行, 斷行方式要統一一下.

說明

下例中, 邏輯與 (&&) 操作符總位于行尾:

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {

    ...
}
           

複制

4.9 預處理指令

總述

預處理指令不要縮進, 從行首開始.

說明

即使預處理指令位于縮進代碼塊中, 指令也應從行首開始.

// 好 - 指令從行首開始
    if (lopsided_score) {
#if DISASTER_PENDING      // 正确 - 從行首開始
    DropEverything();
# if NOTIFY               // 非必要 - # 後跟空格
    NotifyClient();
# endif
#endif
    BackToNormal();
  }

// 差 - 指令縮進
  if (lopsided_score) {
    #if DISASTER_PENDING  // 差 - "#if" 應該放在行開頭
    DropEverything();
    #endif                // 差 - "#endif" 不要縮進
    BackToNormal();
  }
           

複制

4.10 類格式

總述

通路控制塊的聲明依次序是 public:, protected:, private:, 每個都縮進 2 個空格。

說明

類聲明的基本格式如下:

class MyClass : public OtherClass {
  public:      // 注意有一個空格的縮進
    MyClass();  // 标準的兩空格縮進
    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;
};
           

複制

注意事項:

  • 所有基類名應在 80 列限制下盡量與子類名放在同一行.
  • 關鍵詞 public:, protected:, private: 要縮進 2 個空格.
  • 除第一個關鍵詞 (一般是 public) 外, 其他關鍵詞前要空一行. 如果類比較小的話也可以不空.
  • 這些關鍵詞後不要保留白行.
  • public 放在最前面, 然後是 protected, 最後是 private.

4.11 水準留白

總述

水準留白的使用根據在代碼中的位置決定. 永遠不要在行尾添加沒意義的留白。

說明

通用

void f(bool b) {  // 左大括号前總是有空格.
    ...
    int i = 0;  // 分号前不加空格.
    // 清單初始化中大括号内的空格是可選的.
    // 如果加了空格, 那麼兩邊都要加上.
    int x[] = { 0 };
    int x[] = {0};
}

// 繼承與初始化清單中的冒号前後恒有空格.
class Foo : public Bar {
  public:
    /* 對于單行函數的實作, 在大括号内加上空格
    然後是函數實作 */
    Foo(int b) : Bar(), baz_(b) {}  // 大括号裡面是空的話, 不加空格.
    void Reset() { baz_ = 0; }  // 用括号把大括号與實作分開.
    ...
}
           

複制

添加備援的留白會給其他人編輯時造成額外負擔. 是以, 行尾不要留白格. 如果确定一行代碼已經修改完畢, 将多餘的空格去掉; 或者在專門清理空格時去掉。

循環和條件語句

if (b) {          // if 條件語句和循環語句關鍵字後均有空格.
...
} else {          // else 前後有空格.
}while (test) {}   // 圓括号内部不緊鄰空格.

switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) {    // 循環和條件語句的圓括号裡可以與空格緊鄰.
if ( test ) {     // 圓括号, 但這很少見. 總之要一緻.

for ( int i = 0; i < 5; ++i ) {
    for ( ; i < 5 ; ++i) {  // 循環裡内 ; 後恒有空格, ;  前可以加個空格.
        switch (i) {
        case 1:         // switch case 的冒号前無空格.
        ...
        case 2: break;  // 如果冒号有代碼, 加個空格.
        }
    }
}
           

複制

操作符

// 指派運算符前後總是有空格.
x = 0;

// 其它二進制操作符也前後恒有空格, 不過對于表達式的子式可以不加空格.
// 圓括号内部沒有緊鄰空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

// 在參數和一進制操作符之間不加空格.
x = -5;
++x;

if (x && !y) {
    ...
}
           

複制

模闆和轉換

// 尖括号(< and >) 不與空格緊鄰, < 前沒有空格, > 和 ( 之間也沒有.
vector<string> x;
y = static_cast<char*>(x);

// 在類型與指針操作符之間留白格也可以, 但要保持一緻.
vector<char *> x;
           

複制

類資料成員與函數成員

總述

一般情況下,在類中函數成員與資料成員之間要一行留白,便于檢視。

說明

class Student {
  public:
    GetStuName();
    ShowScore();

    String m_name;
    int m_score;
}
           

複制

4.12 垂直留白

總述

垂直留白越少越好.

說明

這不僅僅是規則而是原則問題了: 不在萬不得已, 不要使用空行. 尤其是: 兩個函數定義之間的空行不要超過 2 行, 函數體首尾不要留白行, 函數體中也不要随意添加空行.

基本原則是: 同一屏可以顯示的代碼越多, 越容易了解程式的控制流. 當然, 過于密集的代碼塊和過于疏松的代碼塊同樣難看, 這取決于你的判斷. 但通常是垂直留白越少越好.

下面的規則可以讓加入的空行更有效:

  • 函數體内開頭或結尾的空行可讀性微乎其微.
  • 在多重 if-else 塊裡加空行或許有點可讀性.

參考

《Google C++風格指南》

持續更新中...

最後

用心感悟,認真記錄,寫好每一篇文章,分享每一框幹貨。願每一篇文章不負自己,不負看客!