带你读《C++语言导学》之一:基 础 知 识-阿里云开发者社区

 知识中心     |      2019-11-07 00:00:00

计算机科学丛书
点击查看第二章
点击查看第三章
C++语言导学(原书第2版)
A Tour of C++, Second Edition
image.png
[美] 本贾尼斯特劳斯特鲁普(Bjarne Stroustrup) 著
王 刚 译

第1章

A Tour of C++, Second Edition

基 础 知 识

首要任务,干掉所有语言专家。
—《亨利六世》(第二部分)

1.1 引言

本章简要介绍C++的符号系统、C++的内存模型和计算模型以及将代码组织为程序的基本机制。这些语言设施支持最为常见的C语言编程风格,我们称之为过程式编程(procedural programming)。

1.2 程序

C++是一种编译型语言。为了让程序运行,首先要用编译器处理源代码文本,生成目标文件,然后再用连接器将目标文件组合成可执行程序。一个C++程序通常包含多个源代码文件,通常简称为源文件(source file)。
image.png
可执行程序都是为特定的硬件/系统组合创建的,不具可移植性。比如说,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++程序如下所示:
image.png
这段代码定义了一个名为main的函数,该函数既不接受任何参数,也不做什么实际工作。
在C++中,花括号{}表示成组的意思,上面的例子里,它指出函数体的首尾边界。从双斜线//开始直到该行结束是注释,注释只供人阅读和参考,编译器会直接略过注释。
每个C++程序必须有且只有一个名为main()的全局函数,它是程序执行的起点。如果main()返回一个int整数值,则它是程序返回给“系统”的值。如果main()不返回任何内容,则系统也会收到一个表示程序成功完成的值。main()返回非零值表示程序执行失败。并非每个操作系统和执行环境都会利用这个返回值:基于Linux/Unix的环境通常会用到,而基于Windows的环境很少会用到。
通常情况下,程序会产生一些输出。例如,下面这个程序输出Hello, World!:
image.png

#include<iostream>

这一行指示编译器把iostream中涉及的标准流I/O设施的声明包含(include)进来。如果没有这些声明的话,表达式
image.png
无法正确执行。运算符<<(“输出”)把它的第二个参数写入到第一个参数。在这个例子里,字符串字面值"Hello, World!n"被写入到标准输出流std::cout。字符串字面值是指被一对双引号包围的字符序列。在字符串字面常量中,反斜线紧跟另一个字符组成一个“特殊字符”。在这个例子中,n是换行符,因此最终的输出结果是Hello, World!后跟一个换行。
std::指出名字cout可在标准库名字空间(参见3.4节)中找到。本书在讨论标准特性时通常会省略掉std::,3.4节将介绍如何不使用显式限定符而让名字空间中的名字可见。
基本上所有可执行代码都要放在函数中,并且被main()直接或间接地调用。例如:
image.png
“返回类型”void表示函数print_square( )不返回任何值。

1.3 函数

在C++程序中完成某些任务的主要方式就是调用函数。你若想描述如何进行某个操作,把它定义成函数是标准方式。注意,函数必须先声明后调用。
一个函数声明需要给出三部分信息:函数的名字、函数的返回值类型(如果有的话)以及调用该函数必须提供的参数数量和类型。例如:
image.png
在一个函数声明中,返回类型位于函数名之前,参数类型位于函数名之后,并用括号包围起来。
参数传递的语义与初始化的语义是相同的(参见3.6.1节)。即,编译器会检查参数的类型,并且在必要时执行隐式参数类型转换(参见1.4节)。例如:
image.png
我们不应低估这种编译时检查和类型转换的价值。
函数声明可以包含参数名,这有助于读者理解程序的含义。但实际上,除非该声明同时也是函数的定义,否则编译器会简单忽略参数名。例如:
image.png
返回类型和参数类型属于函数类型的一部分。例如:
image.png
函数可以是类的成员(参见2.3节和4.2.1节)。对这种成员函数(member function),类名也是函数类型的一部分,例如:
image.png
我们都希望自己的代码易于理解,因为这是提高代码可维护性的第一步。而令程序易于理解的第一步,就是将计算任务分解为有意义的模块(用函数和类表达)并为它们命名。这样的函数就提供了计算的基本词汇,就像类型(包括内置类型和用户自定义类型)提供了数据的基本词汇一样。C++标准算法(如find、sort和iota)提供了一个良好开端(参见第12章),接下来我们就能用这些表示通用或者特殊任务的函数组合出更复杂的计算模块了。
代码中错误的数量通常与代码的规模和复杂程度密切相关,多使用一些更短小的函数有助于降低代码的规模和复杂度。例如,通过定义函数来执行一项专门任务,在其他代码中我们就不必再为其编写一段对应的特定代码,将任务定义为函数促使我们为这些任务命名并明确它们的依赖关系。
如果程序中存在名字相同但参数类型不同的函数,则编译器会为每次调用选择最恰当的版本。例如:
image.png
如果存在两个可供选择的函数且它们难分优劣,则编译器认为此次调用具有二义性并报错。例如:
image.png
定义多个具有相同名字的函数就是我们所熟知的函数重载(function overloading),它是泛型编程(参见7.2节)的一个基本部分。当重载函数时,应保证所有同名函数都实现相同的语义。print()函数就是一个这样的例子:每个print()都将其实参打印出来。

1.4 类型、变量和算术运算

每个名字、每个表达式都有自己的类型,类型决定了能对名字和表达式执行的操作。例如,下面的声明
image.png
指定inch的类型为int,也就是说,inch是一个整型变量。

  • 一个声明(declaration)是一条语句,为程序引入一个实体,并为该实体指明类型:
  • 一个类型(type)定义了一组可能的值以及一组(对象上的)操作。
  • 一个对象(object)是存放某种类型值的内存空间。
  • 一个值(value)是一组二进制位,具体的含义由其类型决定。
  • 一个变量(variable)是一个命名的对象。

C++就像一个小型动物园,提供了各种基本类型,但我不是一个动物学家,因此在这里不会列出全部的C++基本类型。你可以在网络上的参考资料中找到它们,如[Stroustrup,2003]或[Cppreference]。一些例子如下:
image.png
每种基本类型都直接对应硬件设施,具有固定的大小,这决定了其中所能存储的值的范围:
image.png
一个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 算术运算

算术运算符可用于上述基本类型的恰当组合:
image.png
比较运算符也是如此:
image.png
除此之外,C++还提供了逻辑运算符:
image.png
位逻辑运算符对运算对象逐位计算,产生结果的类型与运算对象的类型一致。逻辑运算符&&和||根据运算对象的值返回true或者false。
在赋值运算和算术运算中,C++会在基本类型之间进行有意义的转换,以便它们能自由地混合运算:
image.png
表达式中使用的类型转换称为常规算术类型转换(usual arithmetic conversion),其目的是确保表达式以运算对象中最高的精度进行计算。例如,对一个double和一个int求和,执行的是双精度浮点数的加法。
注意,=是赋值运算符,而==是相等性检测。
除了常规的算术和逻辑运算符,C++还提供了更特殊的修改变量的运算:
image.png
这些运算符简洁、方便,因此使用非常频繁。
表达式的求值顺序是从左至右的,赋值操作除外,它是从右至左求值的。不幸的是,函数实参的求值顺序是未指定的。

1.4.2 初始化

在使用对象之前,必须给它赋予一个值。C++提供了多种表达初始化的符号,如前面用到的=,以及一种更通用的形式—花括号限界的初始值列表:
image.png
=初始化是一种比较传统的形式,可追溯到C语言,但如果你心存疑虑,那么还是使用通用的{}列表形式。抛开其他不谈,这至少可以令你避免在类型转换中丢失信息:
image.png
不幸的是,丢失信息的类型转换,即收缩转换(narrowing conversion),如double转换为int及int转换为char,在C++中是允许的,而且是隐式应用的。隐式收缩转换带来的问题是为了与C语言兼容而付出的代价(参见16.3节)。
我们不可以漏掉常量(参见1.6节)初始化,变量也只有在极其罕见的情况下可以不初始化。也就是说,在引入一个名字时,你应该已经为它准备好了一个合适的值。用户自定义类型(如string、vector、Matrix、Motor_controller和Orc_warrior)可以定义为隐式初始化方式(参见4.2.1节)。
在定义一个变量时,如果它的类型可以由初始值推断得到,则你无须显式指定:
image.png
当使用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节)创建的对象。例如:
image.png
我们必须先构造(初始化)对象,然后才能使用它,对象在作用域的末尾被销毁。对于名字空间对象来说,它的销毁点在整个程序的末尾。对于成员来说,它的销毁点依赖于它所属对象的销毁点。用new创建的对象一直“存活”到delete(参见4.2.2节)销毁了它为止。

1.6 常量

C++支持两种不变性概念:
const:大致的意思是“我承诺不改变这个值”。主要用于说明接口,使得在用指针和引用将数据传递给函数时就不必担心数据会被改变了。编译器强制执行const做出的承诺。const的值可在运行时计算。
constexpr:大致的意思是“在编译时求值”。主要用于说明常量,以允许将数据置于只读内存中(不太可能被破坏)以及提升性能。constexpr的值必须由编译器计算
例如:
image.png
如果某个函数被用在常量表达式中(constant expression),即该表达式在编译时求值,则这个函数必须定义成constexpr。例如:
image.png
constexpr函数可以接受非常量参数,但此时其结果不再是一个常量表达式。当程序的上下文不要求常量表达式时,我们可以使用非常量表达式参数来调用constexpr函数,这样就不用将本来相同的函数定义两次了:一次用于常量表达式,另一次用于变量。
要想定义成constexpr,函数必须非常简单、无副作用且仅使用通过参数传递的信息。特别是,函数不能更改非局部变量,但可以包含循环以及使用自己的局部变量。
例如:
image.png
在某些场合中,常量表达式是语言规则所要求的(如数组的界(参见1.7节)、case标签(参见1.8节)、模板值参数(参见6.2节)以及使用constexpr声明的常量)。另一些情况下使用常量表达式是因为编译时求值对程序的性能非常重要。即使不考虑性能因素,不变性概念(对象状态不发生改变)也是一个重要的设计考量。

1.7 指针、数组和引用

最基本的数据集合类型就是数组—一种空间连续分配的相同类型的元素序列。这基本上就是硬件所提供的机制。元素类型为char的数组可像下面这样声明:
image.png
类似地,指针可这样声明:
image.png
在声明语句中,[]表示“……的数组”,*表示“指向……”。所有数组的下标都从0开始,因此v包含6个元素,从v[0]到v[5]。数组的大小必须是一个常量表达式(参见1.6节)。一种指针变量中存放着一个对应类型的对象的地址:
image.png
在表达式中,前置一元运算符*表示“……的内容”,而前置一元运算符&表示“……的地址”。可以用下面的图形来表示上述初始化定义的结果。
image.png
考虑将一个数组的10个元素拷贝给另一个数组的任务:
image.png
上面的for语句可以这样解读:“将i置为0。当i不等于10时,拷贝第i个元素并递增i”。当作用于一个整型或浮点型变量时,递增运算符++执行简单的加1操作。C++还提供了一种更简单的for语句,称为范围for语句,它可以用最简单的方式遍历一个序列:
image.png
第一个范围for语句可以解读为“从头到尾遍历v的每个元素,将其副本放入x并打印”。注意,当我们使用一个列表初始化数组时,无须指定其大小。范围for语句可用于任意的元素序列(见12.1节)。
如果不希望将值从v拷贝到变量x中,而只是令x引用一个元素,则可编写如下代码:
image.png
在声明语句中,一元后置运算符&表示“……的引用”。引用类似于指针,唯一的区别是我们无须使用前置运算符*访问所引用的值。而且,一个引用在初始化之后就不能再引用其他对象了。
当指定函数的参数时,引用特别有用。例如:
image.png
通过使用引用,我们保证在调用sort(my_vec)时不会拷贝my_vec,从而真正对my_vec进行排序而不是对其副本进行排序。
还有一种情况,我们既不想改变实参,又希望避免参数拷贝的代价,此时应该使用const引用(参见1.6节)。例如:
image.png
函数接受const引用类型的参数是非常普遍的。
用于声明语句中的运算符(如&、*和[])称为声明运算符(declarator operator):
image.png
空指针
我们的目标是确保指针永远指向某个对象,这样该指针的解引用操作才是合法的。当确实没有对象可指向或者需要表示“没有对象可用”的概念时(例如,到达列表的末尾),我们赋予指针值nullptr(“空指针”)。所有指针类型都共享同一个nullptr:
image.png
接受一个指针实参时检查一下它是否指向某个东西,这通常是一种明智的做法:
image.png
有两点值得注意:一是如何使用++将指针移动到数组的下一个元素;二是在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语句:
image.png
image.png
while语句重复执行,直到其循环条件变成false为止。
对数值的检验(例如count_x()中的while(p))等价于将数值与0进行比较(例如while(p!=0))。对指针值的检验(如if(p))等价于将指针值与nullptr进行比较(如if(p!=nullptr))。
“空引用”是不存在的。一个引用必须指向一个合法的对象(C++实现也都假定这一点)。的确存在聪明但晦涩难懂的能违反这条规则的方法,但不要这么做。

1.8 检验

C++提供了一套用于表达选择和循环结构的常规语句,如if语句、switch语句、while循环和for循环。例如,下面是一个简单的函数,它首先向用户提问,然后根据用户的响应返回一个布尔值:
image.png
与<<输出运算符(“放入”)相匹配,>>运算符(“从…获取”)被用于输入;cin是标准输入流(参见第10章)。>>的右侧运算对象是输入操作的目标,其类型决定了>>接受什么输入。输出字符串末尾的n字符表示换行(参见1.2.1节)。
注意,变量answer的定义出现在需要该变量的地方(而非提前)。而声明则可以出现在任意位置。
可以进一步完善代码,使其能够处理用户回答n(表示“no”)的情况:
image.png
image.png
switch语句检验一个值是否存在于一组常量中。这些常量被称为case标签,彼此之间不能重复,如果待检验的值不等于任何case标签,则执行default分支。如果程序也没有提供default,则什么也不做。
在使用switch语句的时候,如果想退出某个case分支,不必从当前函数返回。通常,我们只是希望继续执行switch语句后面的语句,为此只需使用一条break语句。举个例子,考虑下面的这个非常聪明但还比较原始的简单命令行方式电子游戏的分析器:
image.png
类似for语句(参见1.7节),if语句可引入变量并进行检验。例如:
image.png
在本例中,我们定义整数n是用在if语句内,用v.size()对其初始化,并在分号之后立即检验条件n!=0。对一个在条件中声明的名字,其作用域在if语句的两个分支内。
与for语句一样,在if语句的条件中声明名字的目的也是限制变量的作用域,以提高可读性、尽量减少错误。
最常见的情况是检验变量是否为0(或nullptr)。为此,我们可以简单地省略条件的显式描述。例如:
image.png
应尽可能选择使用这种简洁的形式。

1.9 映射到硬件

C++提供到硬件的直接映射。当使用一个基本运算时,其具体实现就是硬件提供的,通常是单一机器运算。例如,两个int相加的运算x+y就是执行一条整数加法机器指令。
C++实现将机器内存看作一个内存位置序列,可在其中存放(有类型的)对象并可使用指针寻址:
image.png
指针在内存中表示为一个机器地址,因此在上图中p的数值为3。如果你觉得这看起来很像一个数组(参见1.7节),那是因为数组就是C++中对“内存中对象的连续序列”的基本抽象。
基本语言结构到硬件的简单映射对原始的底层性能是至关重要的,C和C++多年来就是以此著称的。C和C++的基本机器模型是基于计算机硬件而非某种形式的数学。

1.9.1 赋值

内置类型的赋值就是一条机器拷贝指令。考虑下面的代码:
image.png
这是很明显的,可图示如下:
image.png
注意,两个对象是独立的。改变y的值不会影响到x的值。例如,x=99不会改变y的值。这不仅对int成立,对其他所有类型都成立,在这一点上,C++类似C而与Java、C#等语言不同。
如果希望不同对象引用相同的(共享)值,就必须显式说明。一种方式是使用指针:
image.png
这段代码的效果可图示如下:
image.png
我随意选取了88和92作为两个int的地址。再次强调,我们可以看到被赋值对象从赋值对象得到了值,产生了两个具有相同值独立的对象(在本例中是两个指针)。即,p=q导致p==q。在p=q赋值之后,两个指针都指向y。
引用和指针都是引用/指向一个对象,在内存中都表示为一个机器地址。但是,使用它们的语言规则是不同的。给一个引用赋值不会改变它引用了什么,而是给它引用的对象赋值:
image.png
这段代码的效果可图示如下:
image.png
为了访问一个指针指向的值,你需要使用*;而对于引用,这是自动(隐式)完成的。
对于所有内置类型和提供了=(赋值)和==(相等判断)的定义良好的用户自定义类型(参见第2章),在x=y赋值之后,都有x==y。

1.9.2 初始化

初始化与赋值不同。一般而言,正确执行赋值之后,被赋值对象必须有一个值。而另一方面,初始化的任务是将一段未初始化的内存变为一个合法的对象。对几乎所有的类型来说,读写一个未初始化的变量的结果都是未定义的。对内置类型来说,这个问题对引用来说更为明显:
image.png
image.png
幸运的是,我们不能使用一个未初始化的引用。如果可以的话,r2=99就会将99赋予某个未指定的内存位置。这最终可能导致糟糕的结果或程序崩溃。
你可以使用=初始化一个引用,但不要被这种形式所迷惑。例如:
image.png
这仍然是一个初始化操作,将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]。