1. 视C++为一个语言联邦
C语言
Object-Oriented C++
Template C++。这是C++的泛型编程
STL。STL是个template程序库,
2.
尽量以const,enum,inline替换#define
1 #define ASPECT_RATIO 1.653
记号名称ASPECT_RATIO也许从未被编译器看见:也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT_RATI0有可能没进入记号表(symbol
table)内。
解决之道是以一个常量替换上述的宏(#define):
1 const double AspectRatio 1.653 :
作为一个语言常量,AspectRatio肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量(floating
point
constant,就像本例)而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目地将宏名称ASPECT
RATIO替换为1.653”可能导致目标码(object
code)出现多份1.653,若改用常量AspectRatio绝不会出现相同情况。
两种特殊情况
定义常量指针,常量定义式放在头文件内,指针要声明为const,
1 2 3 4 const char * const authorName = "Scott" ;const std::string authorName ("Scott" ) ;
2. class专属常量
1. 常量的作用域限制于class内,必须让其成为class的一个成员;为确保此常量纸多只有一个实体,必须让他成为一个static成员:
1 2 3 4 5 6 7 8 9 class GamePlayer {private : statin const int NumTurns = 5 ; int scores[Numturns]; };const int GamePlayer::NumTurns;
2. C++要求对所使用的任何东西提供一个定义式,但如果他是个class专属常量又是static且为整数类型(int,char,bool),则需要特殊处理。只要不取他们的地址,可以声明并使用而无需提供定义式
3. 如果取某个class专属常量的地址,或者不取地址而编译器要坚持看到一个定义式,就要使用定义式
4. 定义式放入实现文件而非头文件,由于声明时已经有初值,因此定义时不可以再设初值
5. 我们无法利用#define创建一个class专属常量,因为#define不重视作用域
6. 若编译器不预序static整数型class常量,可用枚举类型补偿
1 2 3 4 5 6 7 class GamePlayer {private : enum { NumTurns = 5 }; int scores[Numturns]; };
7. enum hack的行为更像是#define而不像const,因为取enum的地址不合法,如果不想让别人通过指针或者引用指向某个整数变量,enum可以实现。
define误用情况实现宏
宏中的所有实参要加上小括号,否则回出问题,即使加了小括号,也会有问题
1 2 3 4 5 template <typename T>inline void callWithMax (const T& a, const T& b) { f (a > b ? a : b); }
2. 对于形似函数的宏,最好改用inline函数替换#define
3. 尽可能使用const
const允许指定一个语义约束(不被改动的对象),编译器会强制实施这项约束。
const在左边,表示被指物是常量,如果在 右边,表示指针自身是常量,如果出现在型号两边,表示被指物和指针两者都是常量。
被指物是常量,const写在类型之前或者类型之后星号之前。
1 2 void f1 (const Widget* pw) ;void f2 (Widget const * pw) ;
STL迭代器作用就像T*指针。不能指向不同的东西,但指向的东西的值可以改动
1 const std::vector<int >::iterator iter = vec.begin ();
1. 若希望所指的东西不能改变,,需要用const_iterator,类似于const T*指针
const成员函数
将const实施于成员函数的目的,是为了确认该成员函数可作用域const对象身上
它们使class接口比较容易被理解
使“操作const对象成为”可能
两个成员函数如果只是常量性不同,可以被重载
const对象大多用于passed by pointer-to-const或者passed by
reference-to-const的传递结果
如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法
利用c++的一个与const相关的摆动场:mutable(可变的),mutable释放掉non-static成员变量的bitwise
constness约束
在const和non-const成员函数中避免重复
const成员函数承诺绝不改变其对象的逻辑状态,如果在const函数内调用non-const函数,就会冒风险
将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise
constness,但你编写程序时应该使用“概念上的常量性”
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复
4. 确定对象使用前已先被初始化
读取未初始化的值会导致不明确的行为。
在某些平台上,读取未初始化的值,可能使程序终止
可能读入一些半随机的bits,污染了正在进行读取操作的那个对象,最终导致不可预知的程序行为,以及令人不愉快的调试过程
永远在适用对象之前先将它初始化。对于无任何成员的内置类型,必须手工完成,确保每一个构造函数都将对象的每一个成员初始化
1 2 3 4 5 int x = 0 ;const char * text = "A C-style string" ;double d; std::cin >> d;
注意赋值和初始化的区别,ABEntry构造函数的一个较佳写法是,使用所谓的成员初值列替换赋值动作:效率更高
1 ABEntry::ABEntry (const std::string& name):theName (name){}
对于大多数类型而言,比起先调用default狗在函数然后再调用copy
assignment操作符,单只调用一次copy构造函数时比较高效的。
要default构造一个成员变量,可以使用成员初值列,只要指定无物作为初始化实参
1 2 3 4 ABEntry::ABEntry () :theName (), numTimesConsulted (0 ) {}
编译器会为用户自定义类型之成员变量自动调用default构造函数——如果那些成员变量在成员初值列中没有被指定初值的话
规定总是在初值列中列出所有成员变量
有些情况,即使成员变量属于内置类型,也一定要使用初值列。如果成员变量时const或者references,就一定要使用初值,不能被赋值
总是使用成员初值列,这样做有时候绝对必要,且又往往比赋值更高效
许多类有多个构造函数,每个构造函数有自己的成员初值列,如果这种类存在许多成员变量或基类,多份成员初值列的存在就会导致重复。这种情况在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常是private),供所有构造函数调用
c++有固定的成员初始化次序,基类更早于派生类被初始化,类成员变量总是以其声明次序被初始化,即使他们在成员初始列中以不同的次序出现。为避免一些晦涩错误,当在成员初值列中条列各个成员时,最好总是以其声明次序为次序(两个成员变量的初始化带有次序性,例如初始化array时要制定大小,因此代表大小的那个成员变量必须先有初值)。
non-local static对象
static对象,寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。包括global、定义于namespace作用域内、在calsses内、在函数内、在file作用域内被声明为static的对象。
函数内的static对象称为local static 对象,其他static对象称为non-local
static对象。程序结束时static对象就会被自动销毁,也就是他们的析构函数会在main()结束时被自动调用。
编译单元是指产出单一目标文件的源码,时单一源码文件加上其所含入的头文件
如果某编译单元内的某个non-local
static对象的初始化动作使用了另一编译单元内的某个non-local
static对象,他所用到的这个对象可能尚未被初始化,因为C++对于定义于不同编译单元内的non-local
static对象的初始化次序并无明确定义
将每个non-local
static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所包含的对象。然后用户调用这些函数。这是Singleton模式的一个常见实现手法。
函数内的local
static对象会在该函数被调用、首次遇上该对象之定义式时被初始化。所以用函数调用(返回一个reference指向local
static对象)替换直接访问non-local
static对象,保证获得的references将指向一个历经初始化的对象。
更棒的是,如果从未调用non-local
static对象的“仿真函数”,就绝不会引发构造和析构成本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class FileSystem {};FileSystem& tfs () { static FileSystem fs; return fs; }class Directory {}; Directory::Directory () { std::size_t disks = tfs ().numDisks (); }Directory& tempDir () { static Directory td; return td; }
这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local
static对象,第二行返回它。如果频繁调用,可以使用inlining。
从另一个角度看,这些函数内含tataic对象,在多线程系统中带有不确定性。
任何一种non-const
static对象,无论它是local还是non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段手工调用所有reference-returning函数,这可以消除与初始化有关的“竞速形势”。
运用reference-returning函数防止“初始化次序问题”,前提是其中有着很一个对对象而言合理的初始化次序。如果有一个系统,其中对象A必须在对象B之前先初始化,但A的初始化能否成功却又受制于B是否已被初始化
在避免对象初始化之前过早地使用它们
手工初始化内置型non-member对象
使用成员初值列对付对象的所有成分
在初始化次序不确定性氛围下加强你的设计(对不同编译单元定义的non-local
static对象是一种折磨)
对内置型对象进行手工初始化,因为c++不保证初始化它们
构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和他们在class中的声明次序相同
为避免跨编译单元之初始化次序问题,用local static对象替换non-local
static对象