C++的四个语言层次:
综述:C++并不是一个带有一组守则的一体语言:它是从四个次语言组成的联绑政府,每个次语言都有自己的规约。
对于单纯常量,最好以const对象或enums替换#define。
对于形似函数的宏,最好改用inline函数替换#define
STL的迭代器是以指针为根据塑模出来,所以迭代器的作用就像个T*的指针。声明迭代器为const就像声明指针为const一样(即声明一个T*const指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果希望迭代器所指的东西是不可被改动的,需要的是const_iterator。
vector<int> vec; const vector<int>::iterator = vec.begin(); *iter = 10; // 没问题,改变iter所指物 ++iter; // 错误!iter是const vector<int>::const_iterator cIter = vec.begin(); *cIter = 10; // 错误!*cIter是const ++cIter; // 没问题,改变cIter
请记住
- 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用哉内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
- 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。
类的构造函数的次序是先执行构造函数初始化列表,初始化所有成员变量,然后再执行构造函数体,构造函数体内的成员赋值已经不属于初始化的范畴,成员都是用拷贝赋值。
如果类没有初始化列表,则类会先执行默认构造函数,构造出所有成员变量后,再执行函数体内的拷贝赋值。
C++类的成员初始化是有着明显的次序的,一般是基类的成员先初始化,然后派生类的成员按定义的顺序初始化。所以类的构造函数初始化列表上的初始化顺序跟类真实的成员初始化顺序是没有关系的。
“不同编译单元内定义之non-local static对象”
static对象,其寿命从被构造出来直到程序结束为止。
函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。
当我们的某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元的某个non-local static对象,它所用到的这个对象可以尚未被初始化。C++关于定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。
比如在a.cpp里我们定义一个类,一个该类的对象
class FileSystem { public: size_t numDisks()const; }; extern FileSystem tfs;
现在同一个项目下的b.cpp文件中有一个类,类构造函数用到了tfs对象。
class Directory { public: Directory(params); }; Directory::Directory(params) { size_t disks = tfs.numDisks(); }
现在如果我们创建了一个Directory对象
Directory tempDir(params);
上面的代码就可能会出问题,除非能保证tfs在tempDir之前先初始化,否则tempDir的构造函数会用到尚未初始化的tfs。
解决方案:
C++保证,函数内的local static对象会在该函数被调用期间,首次遇到该对象的定义的时候被初始化。
所以如我们把tfs和tempDir设计为一个函数,函数返回该类的一个static对象引用就可以解决问题了。
所以我们可以改写上面的代码:
FileSystem& tfs() { static FileSystem fs; return fs; } Directory& tempDir() { static Directory td; return td; }
请记住
- 为内置型对象进行手工初始化,因为C++不保证初始化它们。
- 构造函数最好使用成员初始化列表,而不要在函数体内使用赋值操作。初始列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
- 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。