跳轉到內容

C++ 程式設計

來自 Wikibooks,開放世界中的開放書籍

編碼風格約定

[編輯 | 編輯原始碼]

使用指南或一套約定為程式設計師提供了一套程式碼規範化或編碼風格規則,這些規則規定了如何格式化程式碼、命名變數、放置註釋或任何其他與語言無關的程式碼結構決策。這非常重要,因為您與他人共享專案。透過建立對一組通用的編碼標準和建議達成一致,可以節省時間和精力,透過提高程式碼庫的可理解性和透明度,為未記錄的結構提供共同基礎,從而簡化除錯並提高程式碼的可維護性。這些規則也可以稱為原始碼風格程式碼約定編碼標準或其變體。

許多組織釋出了 C++ 風格指南。可以在C++ 編碼約定參考部分找到不同方法的列表。C++ 程式設計中最常用的風格是 ANSI 或 Allman 風格,而許多 C 程式設計仍然使用 Kernighan 和 Ritchie (K&R) 風格。需要注意的是,這應該是您在專案中做出的第一個決定之一,在民主環境中,達成共識可能非常困難。

Bjarne Stroustrup 關於風格約定的演示影片(不僅僅是 C++14)https://github.com/isocpp/CppCoreGuidelines以及指南連結:https://github.com/isocpp/CppCoreGuidelines%7CCppCoreGuidelines Herb Sutter 關於風格約定的演示影片(不僅僅是 C++14)https://www.youtube.com/watch?v=hEx5DNLWGgA GSL(指南支援庫)連結 https://github.com/Microsoft/GSL


程式設計師傾向於堅持一種編碼風格,他們將其自動化,並且任何偏差都很難適應,如果您沒有喜歡的風格,請嘗試對常用風格進行儘可能小的修改,或者儘可能廣泛地瞭解,以便輕鬆適應變化或為您的方法辯護。有一些軟體可以幫助格式化或美化程式碼,但自動化也存在缺點。如前所述,編譯器完全忽略了縮排和空格或製表符的使用。編碼風格應根據標準化的最低共同需求而有所不同。

另一個因素,即使程度很小,選擇編碼風格約定的因素是 IDE(或程式碼編輯器)及其功能,這可能例如會影響確定程式碼的冗長程度、行的最大長度等。一些編輯器現在具有非常有用的功能,例如單詞補全、重構功能等,這些功能可以使某些規範變得不必要或過時。這將使編碼風格的採用也取決於目的碼使用者可用的軟體。

受程式碼風格選擇影響的領域有

  • 可重用性
    • 自文件程式碼
    • 國際化
    • 可維護性
    • 可移植性
  • 最佳化
  • 構建過程
  • 避免錯誤
  • 安全性
標準化很重要

無論您選擇哪種特定的編碼風格,一旦選擇,都應在同一個專案中保持一致。閱讀遵循不同風格的程式碼可能會變得非常困難。在接下來的部分中,我們將嘗試解釋為什麼某些選項是常見做法,而不會強迫您採用特定的風格。

注意
使用糟糕的編碼風格比根本沒有編碼風格更糟糕,因為您會將不良實踐擴充套件到所有程式碼庫中。

25 行 80 列

[編輯 | 編輯原始碼]

這條規則是普遍推薦的,但經常有人反駁說這條規則已經過時。該規則起源於基於文字的計算機終端和點陣印表機通常最多隻能顯示 80 列文字的時代。因此,超過 80 列的文字要麼會不方便地換行,要麼更糟的是根本無法顯示。

除了裝置的物理限制外,這條規則通常仍然建議在以下論點下提出:如果您編寫的程式碼將超過 80 列或 25 行,那麼就該考慮將程式碼拆分為函數了。較小的封裝程式碼塊有助於審查程式碼,因為它可以一次全部看到,而無需向上或向下滾動。這使程式設計師對專案的思維表示模組化,從而簡化了思維表示。當您必須返回一個您已停止工作 6 個月的專案時,此做法將為您節省寶貴的時間。

例如,您可能希望將長的輸出語句拆分為多行

    fprintf(stdout,"The quick brown fox jumps over the lazy dog. "
                   "The quick brown fox jumps over the lazy dog.\n"
                   "The quick brown fox jumps over the lazy dog - %d", 2);


此推薦做法也與本書0 表示成功函式約定相關,我們將在本書的函式部分介紹。

空白和縮排

[編輯 | 編輯原始碼]

注意
空格、製表符和換行符(換行)稱為空白。空白用於分隔相鄰的單詞和數字;在其他任何地方都被忽略,除了在引號和預處理器指令中。

使用空白來提高程式碼可讀性的約定稱為縮排風格。每個程式碼塊和每個定義都應遵循一致的縮排風格。這通常意味著 {} 中的所有內容。但是,單行程式碼塊也一樣。

使用固定數量的空格進行縮排。建議各不相同;2、3、4、8 都是常見的數字。如果您使用製表符進行縮排,則必須注意編輯器和印表機可能會以不同的方式處理和擴充套件製表符。K&R 標準建議縮排大小為 4 個空格。

製表符的使用是有爭議的,其基本前提是它降低了原始碼的可移植性,因為載入到具有不同設定的不同編輯器中的相同原始碼看起來不會相同。這是某些程式設計師更喜歡使用空格的一致性(或配置編輯器以用必要的空格數替換製表鍵的使用)的主要原因之一。

例如,程式也可以用以下方式編寫

// Using an indentation size of 2
if ( a > 5 )  { b=a; a++; }

但是,使用正確的縮排可以使相同的程式碼更具可讀性

// Using an indentation size of 2
if ( a > 5 )  {
  b = a;
  a++;
}

// Using an indentation size of 4
if ( a > 5 )
{
    b = a;
    a++;
}

大括號(花括號)的位置

[編輯 | 編輯原始碼]

正如我們在語句部分早期看到的那樣,複合語句在 C++ 中非常重要,它們也受不同編碼風格的影響,這些風格建議採用不同的開始和結束大括號({})放置方式。有些建議將開始大括號放在語句所在行的末尾(K&R)。其他一些建議將它們放在單獨的一行,但不縮排(ANSI C++)。GNU 建議將大括號放在單獨的一行,並將其縮排一半。我們建議選擇一種大括號放置風格並堅持使用它。

示例

if (a > 5) {
  // This is K&R style
}

if (a > 5) 
{
  // This is ANSI C++ style
}

if (a > 5) 
  {
    // This is GNU style
  }

註釋是編譯器忽略的程式碼部分,允許使用者在原始碼的相關區域進行簡單的註釋。註釋可以是塊的形式,也可以是單行的形式。

  • 單行註釋(非正式地,C++ 樣式),以//開頭,一直持續到行尾。如果註釋行中的最後一個字元是\,則註釋將繼續到下一行。
  • 多行註釋(非正式地,C 樣式),以/*開頭,以*/結束。

注意
自從 1999 年修訂版以來,C 也允許使用C++ 風格的註釋,因此非正式名稱在很大程度上具有歷史意義,用於區分這兩種註釋方法。

我們現在將描述如何在原始碼中添加註釋,但不會描述在哪裡、如何以及何時註釋;我們將在稍後詳細介紹。

C 風格註釋

[編輯 | 編輯原始碼]

如果您使用 C 風格註釋,請嘗試像這樣使用它

註釋單行

/*void EventLoop(); /**/

註釋多行

/*
void EventLoop();
void EventLoop();
/**/

這允許您輕鬆取消註釋。例如

取消註釋單行

void EventLoop(); /**/

取消註釋多行

void EventLoop();
void EventLoop();
/**/

注意
某些編譯器可能會生成錯誤/警告。
儘量避免在函式內部使用 C 風格註釋,因為 C 風格註釋不支援巢狀(大多數編輯器現在都具有一定的顏色功能來防止此類錯誤,但很容易錯過,並且不應該假設程式碼是如何讀取的)。

... 透過僅刪除註釋的開頭並激活下一個註釋,您確實重新激活了註釋的程式碼,因為如果您以這種方式開始註釋,它將一直有效,直到找到註釋的結束符*/

注意
請記住,C 風格註釋/* like this */ 不“巢狀”,即您不能編寫

int function() /* This is a comment */
{              
 return 0;  
}              and this is the same comment */
               so this isn't in the comment, and will give an error*/

因為文字所以這不在註釋中 */在行尾,不在註釋內部;註釋在第一個*/找到的序列結束,忽略任何中間/*序列,這在人類讀者看來可能像是巢狀註釋的開始。

C++ 風格註釋

[編輯 | 編輯原始碼]

示例

// This is a single one line comment

或者

if (expression) // This needs a comment
{
  statements;   
}
else
{
  statements;
}

反斜槓是續行字元,並將註釋續到下一行

// This comment will also comment the following line \
std::cout << "This line will not print" << std::endl;
使用註釋臨時忽略程式碼

註釋有時也用於包含我們希望編譯器臨時忽略的程式碼。這在查詢程式中的錯誤時非常有用。如果程式沒有產生預期的結果,則可以透過註釋掉程式碼來跟蹤哪個特定語句包含錯誤。

使用C 風格註釋的示例
/* This is a single line comment */

或者

/*
   This is a multiple line comment
*/
CC++ 風格

組合多行註釋 (/* */) 和 c++ 註釋 (//) 以註釋掉多行程式碼

註釋掉程式碼

/*
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
//*/

取消註釋程式碼塊

//*
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
//*/

這是因為//* 仍然是 c++ 註釋。而 //*/ 充當 c++ 註釋和多行註釋終止符。但是,如果任何多行註釋用於函式描述,則此方法無效。

關於使用預處理語句的說明

另一種方法(被認為是不好的做法)是選擇性地啟用或停用程式碼部分

#if(0)   // Change this to 1 to uncomments.
void EventLoop();
#endif

這被認為是不好的做法,因為當混合使用多個 #if 時,程式碼通常變得難以閱讀,如果使用它們,請不要忘記在 #endif 中新增一條註釋,說明它對應哪個 #if

#if (FEATURE_1 == 1)
do_something;
#endif //FEATURE_1 == 1

您可以透過使用內聯函式(通常被認為比宏更易讀,且沒有效能損失)來防止難以閱讀,這些函式在 #if #else #endif 中僅包含兩個部分

inline do_test()
  {
    #if (Feature_1 == 1)
      do_something
    #endif  //FEATURE_1 == 1
  }

並呼叫

do_test();

在程式中

注意
應避免使用單行 C 風格註釋,因為它們被認為已過時。混合使用 C 和 C++ 風格的單行註釋被認為是不好的做法。一個常見的例外是,為了測試/除錯目的,停用單行語句中間的特定程式碼部分,在釋出程式碼中,應刪除此類操作的任何需求。

命名識別符號

[編輯 | 編輯原始碼]

C++ 對識別符號關鍵字的名稱的限制已在程式碼部分中介紹。它們在命名方面提供了很大的自由,可以使用特定的字首或字尾,以大寫或小寫字母開頭名稱,將所有字母都保留為單一大小寫,或者對於複合詞,使用像 "_" 這樣的單詞分隔符或翻轉每個組成詞的首字母的大小寫。

注意
還需要注意避免與作業系統的 API(取決於可移植性要求)或其他標準發生衝突。例如,POSIX 的關鍵字以 "_t" 結尾。

匈牙利命名法
[編輯 | 編輯原始碼]

匈牙利命名法,現在也稱為 Apps 匈牙利命名法,是由 Charles Simonyi(大約 1972 年至 1981 年在施樂帕洛阿爾託研究中心工作的程式設計師,後來成為微軟的首席架構師)發明的;並且直到最近,它一直是大多數微軟程式碼中使用的首要命名約定。它使用字首(例如“m_”表示成員變數,“p”表示指標),而識別符號的其餘部分通常使用某種混合大小寫形式寫出。我們提到此約定是因為您很可能會發現它正在使用,如果您在 Windows 中進行任何程式設計,則更有可能,如果您有興趣瞭解更多資訊,可以檢視維基百科關於此表示法的條目

此表示法被認為已過時,因為它極易出錯,並且需要付出一些努力才能在當今的 IDE 中進行維護,而沒有帶來任何實際好處。如今,重構是一項日常任務,IDE 已發展為提供識別符號彈出視窗和顏色方案使用的幫助。所有這些資訊輔助工具減少了對這種表示法的需求。

前導下劃線
[編輯 | 編輯原始碼]

在大多數情況下,最好避免使用前導下劃線。它們是為編譯器或庫的內部變數保留的,並且可能使您的程式碼的可移植性降低,並且更難以維護。這些變數也可以從庫中剝離(即變數不再可訪問,它對外部世界隱藏),因此除非您想覆蓋庫的內部變數,否則不要這樣做。

重用現有名稱
[編輯 | 編輯原始碼]

不要將標準庫函式和物件的名稱用於您的識別符號,因為這些名稱被視為保留字,並且當以意外的方式使用時,程式可能變得難以理解。

合理的名稱
[編輯 | 編輯原始碼]

始終使用良好、未縮寫、拼寫正確且有意義的名稱。

首選英語(因為 C++ 和大多數庫已經使用英語),並避免使用簡短的隱晦名稱。這將使閱讀和鍵入名稱變得更容易,而不必查詢它。

注意
忽略此規則以用於迴圈變數和在小範圍內 (~20 行) 使用的變數是可以接受的,如果該變數的目的足夠明顯,可以為它們指定簡短的名稱以節省空間。從歷史上看,在這種情況下最常用的變數名稱是“i”。

“i”可能源自“增量”或“索引”一詞。“i”在for迴圈中非常常見,這非常符合此類變數名稱的使用規範。

在早期的 Fortran 編譯器(直到 F77)中,以字母 i 到 n 開頭的變數隱式表示整數 - 並且按照慣例,前幾個 (i、j、k) 通常用作迴圈計數器。

名稱指示用途
[編輯 | 編輯原始碼]

識別符號應指示其表示的變數/函式等的函式,例如,foobar可能不是儲存人員年齡的變數的良好名稱。

識別符號名稱也應該具有描述性。n可能不是表示員工人數的全域性變數的良好名稱。但是,必須找到長名稱和大量鍵入之間的良好平衡。因此,對於在小範圍或上下文中使用的變數,可以放寬此規則。許多程式設計師更喜歡短變數(例如 i)作為迴圈迭代器。

按照慣例,變數名稱以小寫字元開頭。在包含多個自然語言單詞的識別符號中,使用下劃線或大寫來分隔單詞,例如num_chars(K&R 風格)或numChars(Java 風格)。建議您選擇一種表示法,並且不要在一個專案中混合使用它們。

在命名 #defines、常量變數、enum 常量和宏時,使用全大寫字母並以 '_' 分隔符連線;這使得很清楚該值不可更改,並且在宏的情況下,明確表示您正在使用需要謹慎的結構。

注意
有一種廣泛的觀點認為,僅應將 LIKE_THIS 樣式的名稱用於宏,以便宏使用的名稱空間(不尊重 C++ 範圍)不會與用於其他識別符號的名稱空間重疊。與 C++ 命名約定中通常一樣,沒有一個普遍接受的標準。通常,最重要的是保持一致性。

函式和成員函式
[編輯 | 編輯原始碼]

賦予函式和成員函式的名稱應具有描述性,並明確其功能。由於函式和成員函式通常執行操作,因此最佳名稱選擇通常包含動詞和名詞的混合,例如 CheckForErrors() 而不是 ErrorCheck(),以及 dump_data_to_file() 而不是 data_file()。函式和成員函式的清晰和描述性名稱有時可以更容易地正確猜測函式和成員函式的功能,有助於使程式碼更具自文件性。透過遵循此命名約定和其他命名約定,程式可以更自然地閱讀。

在使用包含縮寫的名稱時,人們似乎有非常不同的直覺。最好確定一種策略,使名稱完全可預測。例如,NetworkABCKey。請注意,來自 ABC 的 C 和來自 key 的 K 如何混淆。有些人並不介意這一點,而另一些人則非常討厭,因此您會在不同的程式碼中找到不同的策略,因此您永遠不知道該叫什麼。

字首和字尾有時很有用

  • Min - 表示某物可以具有的最小值。
  • Max - 表示某物可以具有的最大值。
  • Cnt - 某物的當前計數。
  • Count - 某物的當前計數。
  • Num - 某物的當前數量。
  • Key - 鍵值。
  • Hash - 雜湊值。
  • Size - 某物的當前大小。
  • Len - 某物的當前長度。
  • Pos - 某物的當前位置。
  • Limit - 某物的當前限制。
  • Is - 查詢某事物是否為真。
  • Not - 查詢某事物是否不為真。
  • Has - 查詢某事物是否具有特定值、屬性或特性。
  • Can - 查詢某事物是否可以完成。
  • Get - 獲取值。
  • Set - 設定值。

在大多數情況下,最好也避免使用前導下劃線。例如,這些是有效的識別符號

  • i 迴圈值
  • numberOfCharacters 字元數量
  • number_of_chars 字元數量
  • num_chars 字元數量
  • get_number_of_characters() 獲取字元數量
  • get_number_of_chars() 獲取字元數量
  • is_character_limit() 這是字元限制嗎?
  • is_char_limit() 這是字元限制嗎?
  • character_max() 字元的最大數量
  • charMax() 字元的最大數量
  • CharMin() 字元的最小數量

這些也是有效的識別符號,但你能說出它們的意思嗎?

  • num1
  • do_this()
  • g()
  • hxq

以下識別符號有效,但最好避免使用

  • _num 因為它可能被編譯器/系統標頭檔案使用
  • num__chars 因為它可能被編譯器/系統標頭檔案使用
  • main 因為它可能導致混淆
  • cout 因為它可能導致混淆

以下識別符號無效

  • if 因為它是一個關鍵字
  • 4nums 因為它以數字開頭
  • number of characters 因為識別符號內不允許使用空格


顯式或隱式

[編輯 | 編輯原始碼]

這兩種方法都可以支援。如果預設使用隱式,這意味著鍵入次數減少,但也可能對人類讀者造成錯誤的假設,並且對於編譯器(根據具體情況)來說需要做額外的工作,另一方面,如果您編寫更多關鍵字並明確您的意圖,則生成的程式碼將更清晰並減少錯誤(使隱藏的錯誤能夠被發現),或者更明確(自文件化),但這可能也會導致程式碼演進的額外限制(就像我們將看到 const 的使用一樣)。這是一條細線,必須根據專案的性質達成平衡,編輯器、程式碼補全、語法著色和懸停工具提示的功能減少了大部分工作。重要的是,與任何其他規則一樣,保持一致性。

即使成員函式隱式內聯,也選擇使用 inline

除非您計劃修改它,否則您可能最好使用 const 資料型別。編譯器可以輕鬆地透過此限制進行更多最佳化,並且您不太可能意外損壞資料。確保您的方法採用 const 資料型別,除非您絕對必須修改引數。類似地,在實現私有成員資料的訪問器時,在大多數情況下,您應該 return const。這將確保如果您正在操作的物件作為 const 傳遞,則不影響物件中儲存的資料的方法仍按預期工作並可以被呼叫。例如,對於包含一個人的物件,getName() 應該 return const 資料型別,而 walk() 可能是非 const,因為它可能會更改 Person 中的一些內部資料,例如疲勞度。

通常的做法是避免使用 typedef 關鍵字,因為它如果使用不當可能會使程式碼變得模糊,或者可能導致程式設計師意外地誤用大型結構,將其視為簡單型別。如果使用,請為重新命名的型別定義一組規則,並確保記錄它們。

volatile

[編輯 | 編輯原始碼]

此關鍵字告知編譯器,它限定為 volatile(隨時可能更改)的變數不包含在任何最佳化技術中。此變數的使用應保留用於已知由於程式的外部影響(無論是硬體更新、第三方應用程式還是應用程式中的另一個執行緒)而修改的變數。

由於 volatile 關鍵字會影響效能,因此您應該考慮不同的設計以避免這種情況:大多數需要此關鍵字的平臺都提供了一種替代方案,有助於維持可擴充套件的效能。

請注意,使用 volatile 本意並非用作執行緒或同步原語,並且 volatile 變數上的操作也不能保證是原子的。

指標宣告

[編輯 | 編輯原始碼]

由於歷史原因,一些程式設計師將特定用法稱為

// C code style
int *z;

// C++ code style
int* z;

第二種變體是 C++ 程式設計師迄今為止最喜歡的,它將有助於識別 C 程式設計師或遺留程式碼。

反對 C++ 程式碼風格版本的一個論點是在連結多個專案的宣告時,例如

// C code style
int *ptrA, *ptrB;

// C++ code style
int* ptrC, ptrD;

如您所見,在這種情況下,C 程式碼風格更清楚地表明 ptrA 和 ptrB 是指向 int 的指標,而 C++ 程式碼風格則不太清楚 ptrD 是 int 而不是指向 int 的指標。

在 C++ 程式碼中很少使用多個物件的鏈,除了基本型別之外,即使如此,它也不常使用,並且在指標或其他複雜型別中看到它使用的情況極其罕見,因為它會使人類更難直觀地解析程式碼。

// C++ code style
int* ptrC;
int D;

參考文獻

[編輯 | 編輯原始碼]

華夏公益教科書