C++可以看作4种语言的集合:
-
C,
C++完全兼容C,C++基本兼容C。C++继承了C语言的blocks、statements、preprocessor、built-in data types、arrays、pointers等等。 - Object-Oriented C,即C with classes所诉求的:classes、encapsulation、inheritance、polymorphism、virtual function(dynamic binding)等等。
- Template C++,C++的泛型编程部分。
- STL,标准模板库。STL有自身特殊的规范。
C++高效编程守则取决于使用C++的哪一部分。
例子:对于内置类型(C-like)而言, pass-by-value 通常比 pass-by-reference 更高效;但是对于Object-Oriented C++,由于用户自定义构造函数和析构函数的存在, pass-by-reference-to-const 通常更好,运用Template C++时尤其如此,有时甚至无法得知所处理对象的类型;然而我们进入STL时,迭代器和函数对象都是在C指针的基础上塑造出来的,所以对于STL的迭代器和函数对象而言,旧式的C pass-by-value 守则再次适用。
“宁可以编译器替换预处理器”
-
const替换#define,可以保证const变量被编译器看到,有时还可能比使用#define得到更少的码;有两种特殊情况需要注意:
- 定义常量指针(constant pointers)。这样写(注意要有两个const,一个修饰指针,一个修饰指针所指物):
const elemType* const x;
- class专属常量。为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员(member);而为确保此常量至多只有一份实体,你必须让它成为一个static成员。
注意这里class常量在声明时已经获得初值,因此定义时不可以再设初值。但是“in-class初值设定”只允许对整数常量进行。class A { private: static const int n = 5; //declaration int arrayA[n]; }; const int A::n; //definition
注意#define无法用来创建class专属常量,也不能够提供任何封装性。而const成员变量可以。
- 定义常量指针(constant pointers)。这样写(注意要有两个const,一个修饰指针,一个修饰指针所指物):
-
enum替换#define。编译器有时会(错误地)不允许“static整数型class常量”完成“in-class初值设定”,这时就可以用到enum,因为“一个属于枚举类型(enumeratedtype)的数值可权充ints被使用”。上面的类可以这样写:
class A { private: enum {n = 5;} //"the enum hack" int arrayA[n]; };
- enum hack的行为某方面说比较像#define而不像const。例如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。
- enum和#define一样绝不会导致非必要的内存分配。有的编译器就有可能给“整数型const对象”设定另外的存储空间。
- "enum hack" 是template metaprogramming(模板元编程,见条款48)的基础技术。
-
inline替换#define。对于形似函数的宏(macros),最好改用inline函数替换#define。
- 宏看起来像函数,但不会招致函数调用(function call)带来的额外开销。必须记住为宏中的所有实参加上小括号,但即使如此可能还是会在调用宏时遇到麻烦。
- 你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety)——只要你写出template inline函数。
-
关键字const可以在classes外部修饰global或namespace作用域中的常量,或修饰文件、函数、或区块作用域(block scope)中被声明为static的对象。也可以修饰classes内部的static和non-static成员变量。面对指针,也可以指出指针自身、指针所指物,或两者都(或都不)是const:
char greeting[] = "Hello"; char* p = greeting; const char* p = greeting; char* const p = greeting; const char* const p = greeting;
一句话理解const修饰:const是左结合的,若左边为空,则向右结合。
-
在STL中,迭代器的作用类似一个T*指针。声明迭代器为const就像声明指针为const一样(即声明一个T*const指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果希望STL模拟一个const T*指针,需要用到const_iterator:
std::vector<int> vec; ... const std::vector<int>::interator iter = vec.begin(); //类似T* const *iter = 10; //True!iter指向物是可以改变的 ++iter; //False!iter是const std::vector<int>::const_interator cIter = vec.begin(); //类似const T* *cIter = 10; //False!*cIter是const ++cIter; //True!cIter可以改变
-
在一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联。
- 将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。第一,const成员函数使 class 接口比较容易被理解;第二,它们使“操作const对象”成为可能。
- 两个成员函数如果只是常量性(constness)不同,可以被重载。
- 真实程序中const对象大多用于 passed by pointer-to-const 或 passed by reference-to-const 的传递结果。
-
当const和non-const成员函数有着实质等价的实现时,令non-const operator[]调用其const兄弟是一个避免代码重复的安全做法——即使过程中需要一个转型动作;反向做法——令 const版本调用non-const版本以避免重复——是不正确的。
永远在使用对象之前先将它初始化!
-
对于内置类型,必须手动完成初始化。
-
确保每一个构造函数都将对象的每一个成员初始化。
- 注意分清赋值(assignment)和初始化(initialization)。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。构造函数的一个较佳写法是,使用所谓的member initialization list(成员初值列)替换赋值动作。
- 对大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。
- 如果成员变量是const或references,它们就一定需要初值,不能被赋值。
- 在C++中,base classes(基类)更早于其derived classes(派生类)被初始化,而class的成员变量总是以其声明次序被初始化。当你在成员初值列中条列各个成员时,最好总是以其声明次序为次序。
-
将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。以local static对象替换non-localstatic对象。
- 如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。
- static 对象,其寿命从被构造出来直到程序结束为止。函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。
- 编译单元(translation unit)是指产出单一目标文件(single object file)的那些源码。
- 如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。