C++語言導學(原書第2版)
A Tour of C++, Second Edition

[美] 本賈尼斯特勞斯特魯普(Bjarne Stroustrup) 著
王 剛 譯
第1章
基 礎 知 識
首要任務,幹掉所有語言專家。
—《亨利六世》(第二部分)
1.1 引言
本章簡要介紹C++的符号系統、C++的記憶體模型和計算模型以及将代碼組織為程式的基本機制。這些語言設施支援最為常見的C語言程式設計風格,我們稱之為過程式程式設計(procedural programming)。
1.2 程式
C++是一種編譯型語言。為了讓程式運作,首先要用編譯器處理源代碼文本,生成目标檔案,然後再用連接配接器将目标檔案組合成可執行程式。一個C++程式通常包含多個源代碼檔案,通常簡稱為源檔案(source file)。
可執行程式都是為特定的硬體/系統組合建立的,不具可移植性。比如說,Mac上的可執行程式就無法移植到Windows PC上。當談論C++程式的可移植性時,通常是指源代碼的可移植性,即源代碼可以在不同系統上成功編譯并運作。
ISO的C++标準定義了兩類實體:
- 核心語言特性(core language feature),例如内置類型(如char和int)和循環(如for語句和while語句);
- 标準庫元件(standard-library component),比如容器(如vector和map)和I/O操作(如<<和getline())。
每個C++實作都提供标準庫元件,它們其實也是非常普通的C++代碼。換句話說,C++标準庫可以用C++語言本身實作(僅在實作線程上下文切換這樣的功能時才使用少量機器代碼)。這意味着C++在面對大多數高要求的系統程式設計任務時既有豐富的表達力,同時也足夠高效。
C++是一種靜态類型語言,這意味着任何實體(如對象、值、名稱和表達式)在使用時都必須已被編譯器了解。對象的類型決定了能在該對象上執行的操作。
Hello, World!
最小的C++程式如下所示:
這段代碼定義了一個名為main的函數,該函數既不接受任何參數,也不做什麼實際工作。
在C++中,花括号{}表示成組的意思,上面的例子裡,它指出函數體的首尾邊界。從雙斜線//開始直到該行結束是注釋,注釋隻供人閱讀和參考,編譯器會直接略過注釋。
每個C++程式必須有且隻有一個名為main()的全局函數,它是程式執行的起點。如果main()傳回一個int整數值,則它是程式傳回給“系統”的值。如果main()不傳回任何内容,則系統也會收到一個表示程式成功完成的值。main()傳回非零值表示程式執行失敗。并非每個作業系統和執行環境都會利用這個傳回值:基于Linux/Unix的環境通常會用到,而基于Windows的環境很少會用到。
通常情況下,程式會産生一些輸出。例如,下面這個程式輸出Hello, World!:
#include<iostream>
這一行訓示編譯器把iostream中涉及的标準流I/O設施的聲明包含(include)進來。如果沒有這些聲明的話,表達式
無法正确執行。運算符<<(“輸出”)把它的第二個參數寫入到第一個參數。在這個例子裡,字元串字面值"Hello, World!n"被寫入到标準輸出流std::cout。字元串字面值是指被一對雙引号包圍的字元序列。在字元串字面常量中,反斜線緊跟另一個字元組成一個“特殊字元”。在這個例子中,n是換行符,是以最終的輸出結果是Hello, World!後跟一個換行。
std::指出名字cout可在标準庫名字空間(參見3.4節)中找到。本書在讨論标準特性時通常會省略掉std::,3.4節将介紹如何不使用顯式限定符而讓名字空間中的名字可見。
基本上所有可執行代碼都要放在函數中,并且被main()直接或間接地調用。例如:
“傳回類型”void表示函數print_square( )不傳回任何值。
1.3 函數
在C++程式中完成某些任務的主要方式就是調用函數。你若想描述如何進行某個操作,把它定義成函數是标準方式。注意,函數必須先聲明後調用。
一個函數聲明需要給出三部分資訊:函數的名字、函數的傳回值類型(如果有的話)以及調用該函數必須提供的參數數量和類型。例如:
在一個函數聲明中,傳回類型位于函數名之前,參數類型位于函數名之後,并用括号包圍起來。
參數傳遞的語義與初始化的語義是相同的(參見3.6.1節)。即,編譯器會檢查參數的類型,并且在必要時執行隐式參數類型轉換(參見1.4節)。例如:
我們不應低估這種編譯時檢查和類型轉換的價值。
函數聲明可以包含參數名,這有助于讀者了解程式的含義。但實際上,除非該聲明同時也是函數的定義,否則編譯器會簡單忽略參數名。例如:
傳回類型和參數類型屬于函數類型的一部分。例如:
函數可以是類的成員(參見2.3節和4.2.1節)。對這種成員函數(member function),類名也是函數類型的一部分,例如:
我們都希望自己的代碼易于了解,因為這是提高代碼可維護性的第一步。而令程式易于了解的第一步,就是将計算任務分解為有意義的子產品(用函數和類表達)并為它們命名。這樣的函數就提供了計算的基本詞彙,就像類型(包括内置類型和使用者自定義類型)提供了資料的基本詞彙一樣。C++标準算法(如find、sort和iota)提供了一個良好開端(參見第12章),接下來我們就能用這些表示通用或者特殊任務的函數組合出更複雜的計算子產品了。
代碼中錯誤的數量通常與代碼的規模和複雜程度密切相關,多使用一些更短小的函數有助于降低代碼的規模和複雜度。例如,通過定義函數來執行一項專門任務,在其他代碼中我們就不必再為其編寫一段對應的特定代碼,将任務定義為函數促使我們為這些任務命名并明确它們的依賴關系。
如果程式中存在名字相同但參數類型不同的函數,則編譯器會為每次調用選擇最恰當的版本。例如:
如果存在兩個可供選擇的函數且它們難分優劣,則編譯器認為此次調用具有二義性并報錯。例如:
定義多個具有相同名字的函數就是我們所熟知的函數重載(function overloading),它是泛型程式設計(參見7.2節)的一個基本部分。當重載函數時,應保證所有同名函數都實作相同的語義。print()函數就是一個這樣的例子:每個print()都将其實參列印出來。
1.4 類型、變量和算術運算
每個名字、每個表達式都有自己的類型,類型決定了能對名字和表達式執行的操作。例如,下面的聲明
指定inch的類型為int,也就是說,inch是一個整型變量。
- 一個聲明(declaration)是一條語句,為程式引入一個實體,并為該實體指明類型:
- 一個類型(type)定義了一組可能的值以及一組(對象上的)操作。
- 一個對象(object)是存放某種類型值的記憶體空間。
- 一個值(value)是一組二進制位,具體的含義由其類型決定。
- 一個變量(variable)是一個命名的對象。
C++就像一個小型動物園,提供了各種基本類型,但我不是一個動物學家,是以在這裡不會列出全部的C++基本類型。你可以在網絡上的參考資料中找到它們,如[Stroustrup,2003]或[Cppreference]。一些例子如下:
每種基本類型都直接對應硬體設施,具有固定的大小,這決定了其中所能存儲的值的範圍:
一個char變量的實際大小為給定機器上存放一個字元所需的空間(通常是一個8位的位元組),其他類型的大小都是char大小的整數倍。類型的大小是依賴于實作的(即,在不同機器上可能不同),可使用sizeof運算符獲得這個值。例如,sizeof(char)等于1,sizeof(int)通常是4。
數包括浮點數和整數。
- 浮點數是通過小數點(如3.14)或指數(如3e-2)來區分的。
- 整數字面值預設是十進制(如,42表示四十二)。字首0b訓示二進制(基為2)的整數字面值(如0b10101010)。字首0x訓示十六進制(基為16)整數字面值(如0xBAD1234)。字首0訓示八進制(基為8)的整數字面值(如0334)。
為了令長字面常量對人類更易讀,我們可以使用單引号(')作為數字分隔符。例如,π大約為3.14159'26535'89793'23846'26433'83279'50288,如果你更喜歡十六進制,就是0x3.243F'6A88'85A3'08D3。
1.4.1 算術運算
算術運算符可用于上述基本類型的恰當組合:
比較運算符也是如此:
除此之外,C++還提供了邏輯運算符:
位邏輯運算符對運算對象逐位計算,産生結果的類型與運算對象的類型一緻。邏輯運算符&&和||根據運算對象的值傳回true或者false。
在指派運算和算術運算中,C++會在基本類型之間進行有意義的轉換,以便它們能自由地混合運算:
表達式中使用的類型轉換稱為正常算術類型轉換(usual arithmetic conversion),其目的是確定表達式以運算對象中最高的精度進行計算。例如,對一個double和一個int求和,執行的是雙精度浮點數的加法。
注意,=是指派運算符,而==是相等性檢測。
除了正常的算術和邏輯運算符,C++還提供了更特殊的修改變量的運算:
這些運算符簡潔、友善,是以使用非常頻繁。
表達式的求值順序是從左至右的,指派操作除外,它是從右至左求值的。不幸的是,函數實參的求值順序是未指定的。
1.4.2 初始化
在使用對象之前,必須給它賦予一個值。C++提供了多種表達初始化的符号,如前面用到的=,以及一種更通用的形式—花括号限界的初始值清單:
=初始化是一種比較傳統的形式,可追溯到C語言,但如果你心存疑慮,那麼還是使用通用的{}清單形式。抛開其他不談,這至少可以令你避免在類型轉換中丢失資訊:
不幸的是,丢失資訊的類型轉換,即收縮轉換(narrowing conversion),如double轉換為int及int轉換為char,在C++中是允許的,而且是隐式應用的。隐式收縮轉換帶來的問題是為了與C語言相容而付出的代價(參見16.3節)。
我們不可以漏掉常量(參見1.6節)初始化,變量也隻有在極其罕見的情況下可以不初始化。也就是說,在引入一個名字時,你應該已經為它準備好了一個合适的值。使用者自定義類型(如string、vector、Matrix、Motor_controller和Orc_warrior)可以定義為隐式初始化方式(參見4.2.1節)。
在定義一個變量時,如果它的類型可以由初始值推斷得到,則你無須顯式指定:
當使用auto時,我們傾向于使用=初始化,因為其中不會涉及帶來潛在麻煩的類型轉換,但如果你喜歡始終使用{}初始化,也是可以的。
當沒有特殊理由需要顯式指定資料類型時,一般使用auto。在這裡,“特殊理由”包括:
- 該定義位于一個較大的作用域中,我們希望代碼的讀者清楚地看到資料類型;
- 我們希望明确一個變量的範圍和精度(比如希望使用double而非float)。
使用auto可以幫助我們避免備援的代碼,并且無須再書寫長類型名。這一點在泛型程式設計中尤為重要,因為在泛型程式設計中程式員可能很難知道一個對象的确切類型,類型的名字也可能相當長(參見12.2節)。
1.5 作用域和生命周期
聲明語句将一個名字引入到一個作用域中:
- 局部作用域(local scope):聲明在函數(參見1.3節)或者lambda(參見6.3.2節)内的名字稱為局部名字(local name)。局部名字的作用域從聲明它的地方開始,到聲明語句所在的塊的末尾為止。塊(block)用花括号{}限定邊界。函數參數的名字也屬于局部名字。
- 類作用域(class scope):如果一個名字定義在一個類(參見2.2節、2.3節和第4章)中,且位于任何函數(參見1.3節)、lambda(參見6.3.2節)或enum class(參見2.5節)之外,則稱之為成員名字(member name),或類成員名字(class member name)。成員名字的作用域從包含它的聲明的起始{開始,到該聲明結束為止。
- 名字空間作用域(namespace scope):如果一個名字定義在一個名字空間(參見3.4節)内,同時位于任何函數、lambda(參見6.3.2節)、類(參見2.2節、2.3節和第4章)或enum class(參見2.5節)之外,則稱之為名字空間成員名字(namespace member name)。它的作用域從其聲明位置開始,到名字空間結束為止。
聲明在所有結構之外的名字稱為全局名字(global name),我們稱其位于全局名字空間(global namespace)中。
此外,對象也可以沒有名字,比如臨時對象或者用new(參見4.2.2節)建立的對象。例如:
我們必須先構造(初始化)對象,然後才能使用它,對象在作用域的末尾被銷毀。對于名字空間對象來說,它的銷毀點在整個程式的末尾。對于成員來說,它的銷毀點依賴于它所屬對象的銷毀點。用new建立的對象一直“存活”到delete(參見4.2.2節)銷毀了它為止。
1.6 常量
C++支援兩種不變性概念:
const:大緻的意思是“我承諾不改變這個值”。主要用于說明接口,使得在用指針和引用将資料傳遞給函數時就不必擔心資料會被改變了。編譯器強制執行const做出的承諾。const的值可在運作時計算。
constexpr:大緻的意思是“在編譯時求值”。主要用于說明常量,以允許将資料置于隻讀記憶體中(不太可能被破壞)以及提升性能。constexpr的值必須由編譯器計算
例如:
如果某個函數被用在常量表達式中(constant expression),即該表達式在編譯時求值,則這個函數必須定義成constexpr。例如:
constexpr函數可以接受非常量參數,但此時其結果不再是一個常量表達式。當程式的上下文不要求常量表達式時,我們可以使用非常量表達式參數來調用constexpr函數,這樣就不用将本來相同的函數定義兩次了:一次用于常量表達式,另一次用于變量。
要想定義成constexpr,函數必須非常簡單、無副作用且僅使用通過參數傳遞的資訊。特别是,函數不能更改非局部變量,但可以包含循環以及使用自己的局部變量。
在某些場合中,常量表達式是語言規則所要求的(如數組的界(參見1.7節)、case标簽(參見1.8節)、模闆值參數(參見6.2節)以及使用constexpr聲明的常量)。另一些情況下使用常量表達式是因為編譯時求值對程式的性能非常重要。即使不考慮性能因素,不變性概念(對象狀态不發生改變)也是一個重要的設計考量。
1.7 指針、數組和引用
最基本的資料集合類型就是數組—一種空間連續配置設定的相同類型的元素序列。這基本上就是硬體所提供的機制。元素類型為char的數組可像下面這樣聲明:
類似地,指針可這樣聲明:
在聲明語句中,[]表示“……的數組”,*表示“指向……”。所有數組的下标都從0開始,是以v包含6個元素,從v[0]到v[5]。數組的大小必須是一個常量表達式(參見1.6節)。一種指針變量中存放着一個對應類型的對象的位址:
在表達式中,前置一進制運算符*表示“……的内容”,而前置一進制運算符&表示“……的位址”。可以用下面的圖形來表示上述初始化定義的結果。
考慮将一個數組的10個元素拷貝給另一個數組的任務:
上面的for語句可以這樣解讀:“将i置為0。當i不等于10時,拷貝第i個元素并遞增i”。當作用于一個整型或浮點型變量時,遞增運算符++執行簡單的加1操作。C++還提供了一種更簡單的for語句,稱為範圍for語句,它可以用最簡單的方式周遊一個序列:
第一個範圍for語句可以解讀為“從頭到尾周遊v的每個元素,将其副本放入x并列印”。注意,當我們使用一個清單初始化數組時,無須指定其大小。範圍for語句可用于任意的元素序列(見12.1節)。
如果不希望将值從v拷貝到變量x中,而隻是令x引用一個元素,則可編寫如下代碼:
在聲明語句中,一進制後置運算符&表示“……的引用”。引用類似于指針,唯一的差別是我們無須使用前置運算符*通路所引用的值。而且,一個引用在初始化之後就不能再引用其他對象了。
當指定函數的參數時,引用特别有用。例如:
通過使用引用,我們保證在調用sort(my_vec)時不會拷貝my_vec,進而真正對my_vec進行排序而不是對其副本進行排序。
還有一種情況,我們既不想改變實參,又希望避免參數拷貝的代價,此時應該使用const引用(參見1.6節)。例如:
函數接受const引用類型的參數是非常普遍的。
用于聲明語句中的運算符(如&、*和[])稱為聲明運算符(declarator operator):
空指針
我們的目标是確定指針永遠指向某個對象,這樣該指針的解引用操作才是合法的。當确實沒有對象可指向或者需要表示“沒有對象可用”的概念時(例如,到達清單的末尾),我們賦予指針值nullptr(“空指針”)。所有指針類型都共享同一個nullptr:
接受一個指針實參時檢查一下它是否指向某個東西,這通常是一種明智的做法:
有兩點值得注意:一是如何使用++将指針移動到數組的下一個元素;二是在for語句中,如果不需要初始化操作,則可以省略它。
count_x()的定義假定char是一個C風格字元串(C-style string),即,指針指向了一個以零結尾的char數組。字元串字面值中的字元是不可變的,為了能處理count_x("Hello!"),将count_x聲明為一個const char參數。
在舊式代碼中,通常用0和NULL來替代nullptr的功能。不過,使用nullptr能夠避免混淆整數(如0或NULL)和指針(如nullptr)。
在count_x()例子中,對for語句我們并沒有使用初始化部分,是以可以使用更簡單的while語句:
while語句重複執行,直到其循環條件變成false為止。
對數值的檢驗(例如count_x()中的while(p))等價于将數值與0進行比較(例如while(p!=0))。對指針值的檢驗(如if(p))等價于将指針值與nullptr進行比較(如if(p!=nullptr))。
“空引用”是不存在的。一個引用必須指向一個合法的對象(C++實作也都假定這一點)。的确存在聰明但晦澀難懂的能違反這條規則的方法,但不要這麼做。
1.8 檢驗
C++提供了一套用于表達選擇和循環結構的正常語句,如if語句、switch語句、while循環和for循環。例如,下面是一個簡單的函數,它首先向使用者提問,然後根據使用者的響應傳回一個布爾值:
與<<輸出運算符(“放入”)相比對,>>運算符(“從…擷取”)被用于輸入;cin是标準輸入流(參見第10章)。>>的右側運算對象是輸入操作的目标,其類型決定了>>接受什麼輸入。輸出字元串末尾的n字元表示換行(參見1.2.1節)。
注意,變量answer的定義出現在需要該變量的地方(而非提前)。而聲明則可以出現在任意位置。
可以進一步完善代碼,使其能夠處理使用者回答n(表示“no”)的情況:
switch語句檢驗一個值是否存在于一組常量中。這些常量被稱為case标簽,彼此之間不能重複,如果待檢驗的值不等于任何case标簽,則執行default分支。如果程式也沒有提供default,則什麼也不做。
在使用switch語句的時候,如果想退出某個case分支,不必從目前函數傳回。通常,我們隻是希望繼續執行switch語句後面的語句,為此隻需使用一條break語句。舉個例子,考慮下面的這個非常聰明但還比較原始的簡單指令行方式電子遊戲的分析器:
類似for語句(參見1.7節),if語句可引入變量并進行檢驗。例如:
在本例中,我們定義整數n是用在if語句内,用v.size()對其初始化,并在分号之後立即檢驗條件n!=0。對一個在條件中聲明的名字,其作用域在if語句的兩個分支内。
與for語句一樣,在if語句的條件中聲明名字的目的也是限制變量的作用域,以提高可讀性、盡量減少錯誤。
最常見的情況是檢驗變量是否為0(或nullptr)。為此,我們可以簡單地省略條件的顯式描述。例如:
應盡可能選擇使用這種簡潔的形式。
1.9 映射到硬體
C++提供到硬體的直接映射。當使用一個基本運算時,其具體實作就是硬體提供的,通常是單一機器運算。例如,兩個int相加的運算x+y就是執行一條整數加法機器指令。
C++實作将機器記憶體看作一個記憶體位置序列,可在其中存放(有類型的)對象并可使用指針尋址:
指針在記憶體中表示為一個機器位址,是以在上圖中p的數值為3。如果你覺得這看起來很像一個數組(參見1.7節),那是因為數組就是C++中對“記憶體中對象的連續序列”的基本抽象。
基本語言結構到硬體的簡單映射對原始的底層性能是至關重要的,C和C++多年來就是以此著稱的。C和C++的基本機器模型是基于計算機硬體而非某種形式的數學。
1.9.1 指派
内置類型的指派就是一條機器拷貝指令。考慮下面的代碼:
這是很明顯的,可圖示如下:
注意,兩個對象是獨立的。改變y的值不會影響到x的值。例如,x=99不會改變y的值。這不僅對int成立,對其他所有類型都成立,在這一點上,C++類似C而與Java、C#等語言不同。
如果希望不同對象引用相同的(共享)值,就必須顯式說明。一種方式是使用指針:
這段代碼的效果可圖示如下:
我随意選取了88和92作為兩個int的位址。再次強調,我們可以看到被指派對象從指派對象得到了值,産生了兩個具有相同值獨立的對象(在本例中是兩個指針)。即,p=q導緻p==q。在p=q指派之後,兩個指針都指向y。
引用和指針都是引用/指向一個對象,在記憶體中都表示為一個機器位址。但是,使用它們的語言規則是不同的。給一個引用指派不會改變它引用了什麼,而是給它引用的對象指派:
為了通路一個指針指向的值,你需要使用*;而對于引用,這是自動(隐式)完成的。
對于所有内置類型和提供了=(指派)和==(相等判斷)的定義良好的使用者自定義類型(參見第2章),在x=y指派之後,都有x==y。
1.9.2 初始化
初始化與指派不同。一般而言,正确執行指派之後,被指派對象必須有一個值。而另一方面,初始化的任務是将一段未初始化的記憶體變為一個合法的對象。對幾乎所有的類型來說,讀寫一個未初始化的變量的結果都是未定義的。對内置類型來說,這個問題對引用來說更為明顯:
幸運的是,我們不能使用一個未初始化的引用。如果可以的話,r2=99就會将99賦予某個未指定的記憶體位置。這最終可能導緻糟糕的結果或程式崩潰。
你可以使用=初始化一個引用,但不要被這種形式所迷惑。例如:
這仍然是一個初始化操作,将r綁定到x,而不是任何形式的值拷貝。
初始化和指派的差別對很多使用者自定義類型也是十分重要的,例如string和vector,其中被指派對象擁有資源,而該資源最終需要釋放(參見5.3節)。
參數傳遞和函數傳回值的基本語義是初始化(參見3.6節)。例如,傳引用方式的參數傳遞就是如此。
1.10 建議
本章的建議是《C++Core Guidelines》[Stroustrup,2015]中的建議的一個子集。對那本書的引用是這種形式[CG: ES.23],意為“Expressions and Statement”一節中的第23條準則。一般地,每條核心準則都進一步給出了原理闡述和示例。
[ 1 ] 不必慌張!随着時間推移一切都會清晰起來;1.1節;[CG: In.0]。
[ 2 ] 不要排他地、單獨地使用内置特性。正相反,最佳的方式通常是通過庫(例如ISO C++标準庫,參見第8~15章)間接地使用基本(内置)特性;[CG: P.10]。
[ 3 ] 要想寫出好的程式,你不必了解C++的所有細節。
[ 4 ] 請關注程式設計技術,而非語言特性。
[ 5 ] 關于語言定義問題的最終結論,盡在ISO C++标準;16.1.3節;[CG: P.2]。
[ 6 ] 把有意義的操作“打包”成函數,并給它起個好名字;1.3節;[CG: F.1]。
[ 7 ] 一個函數最好隻執行單一邏輯操作;1.3節;[CG: F.2]。
[ 8 ] 保持函數簡潔;1.3節;[CG: F.3]。
[ 9 ] 當幾個函數對不同類型執行概念上相同的任務時,使用重載;1.3節。
[10] 如果一個函數可能需要在編譯時求值,那麼将它聲明為constexpr;1.6節;[CG: F.4]。
[11] 了解語言原語是如何映射到硬體的;1.4節、1.7節、1.9節、2.3節、4.2.2節、4.4節。
[12] 使用數字分隔符令大的字面值更可讀;1.4節;[CG: NL.11]。
[13] 避免複雜表達式;[CG: ES.40]。
[14] 避免收縮轉換;1.4.2節;[CG: ES.46]。
[15] 最小化變量的作用域;1.5節。
[16] 避免使用“魔法常量”,盡量使用符号化的常量;1.6節;[CG: ES.45]。
[17] 優先采用不可變資料;1.6節;[CG: P.10]。
[18] 一條語句(隻)聲明一個名字;[CG: ES.10]。
[19] 保持公共的和局部名字簡短,特殊的和非局部名字則長一些;[CG: ES.7]。
[20] 避免使用形似的名字;[CG: ES.8]。
[21] 避免出現字母全是大寫的名字;[CG: ES.9]。
[22] 在聲明語句中使用命名類型時,優先使用{}初始化文法;1.4節;[CG: ES.23]。
[23] 使用auto來避免重複類型名;1.4.2節;[CG: ES.11]。
[24] 避免未初始化變量;1.4節;[CG: ES.20]。
[25] 保持作用域盡量小;1.5節;[CG: ES.5]。
[26] 在if語句的條件中聲明變量時,優先采用隐式檢驗而不是與0進行比較;1.8節。
[27] 隻對位運算使用unsigned;1.4節;[CG: ES.101] [CG: ES.106]。
[28] 指針的使用盡量簡單、直接;1.7節;[CG: ES.42]。
[29] 使用nullptr而非0或NULL;1.7節;[CG: ES.47]。
[30] 聲明變量時,必須有值可對其初始化;1.7節、1.8節;[CG: ES.21]。
[31] 可用代碼清晰表達的就不要放在注釋中說明;[CG: NL.1]。
[32] 用注釋陳述意圖;[CG: NL.2]。
[33] 維護一緻的縮進風格;[CG: NL.4]。