条款01:视C++为一个语言联邦
C:区块、语句、预处理器、内置数据类型、数组、指针
面向对象:类、封装、继承、多态、虚函数
模板:泛型编程
STL:容器、迭代器、算法、函数对象
条款02:尽量以const,enum,inline替换#define
#define ASPECT_RATIO 1.653
#define由预处理器处理,不被视为语言的一部分。当运用此常量但获得一个编译错误信息时,也许提到1.653而不是ASPECT_RATIO。这会浪费调试时间
const double AspectRadio = 1.653;
解决之道是以一个常量替换上述的宏
class GamePlayer { enum { NumTurns = 5 }; int scores[NumTurns]; };
当类编译期间需要一个常量,可以使用enum。一个枚举类型的数值可权充int被使用
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) int a = 5, b = 0; CALL_WITH_MAX(++a, b); // a被累加二次 CALL_WITH_MAX(++a, b + 10); // a被累加一次
使用#define实现宏,可能导致逻辑错误
template <typename T> inline void callWithMax(const T &a, const T &b) { f(a > b ? a : b); }
通过模板inline函数可以获得宏带来的效率
条款03:尽可能使用const
vector<int> vec; const vector<int>::iterator it = vec.begin(); *it = 10; it++; // it是const vector<int>::const_iterator it2 = vec.begin(); *it2 = 10; // *it2是const it2++;
使用const指定语义,编译器会强制实施这项约束
class CTextBlock { public: // bitwise const声明,但其实不恰当 char &operator[](size_t pos) const { return pText[pos]; } private: char *pText; }; const CTextBlock cctb("Hello"); char *pc = &cctb[0]; *pc = 'J'; // bitwise constness
编译器强制实施bitwise constness,但是编写程序应该使用conceptual constness
class CTextBlock { public: const char &operator[](size_t pos) const { return pText[pos]; } char &operator[](size_t pos) { return const_cast<char &>(static_cast<const CTextBlock &>(*this)[pos]); } private: char *pText; };
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复
条款04:确认对象被使用前已先被初始化
int x;
在某些语境下x保证被初始化为0,但在其他语境中却不保证
class PhoneNumber {}; class ABEntry { public: ABEntry(const string &name, const string &address, const list<PhoneNumber> &phones); private: string theName; string theAddress; list<PhoneNumber> thePhones; int numTimesConsulted; }; ABEntry::ABEntry(const string &name, const string &address, const list<PhoneNumber> &phones) { theName = name; // 这些都是赋值而非初始化 theAddress = address; thePhones = phones; numTimesConsulted = 0; }
别混淆了赋值和初始化
ABEntry::ABEntry(const string &name, const string &address, const list<PhoneNumber> &phones) : theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) {}
使用成员初始化列表
C++有着十分固定的成员初始化次序。基类早于派生类被初始化,类的成员变量总是以其声明次序被初始化
static对象,其寿命从被构造出来直到程序结束为止。这种对象包括global对象、定义于namespace作用域内的对象、在类内、在函数内、以及在文件作用域类被声明为static的对象。函数内的static对象被称为local static对象,其他static对象被称为non-local static对象。
编译单元是指产出单一目标文件的源码。基本上是单一源码文件加上其所含的头文件。
class FileSystem { public: size_t numDisks() const; }; extern FileSystem fs; class Directory { public: Directory(params); }; Directory::Directory(params) { size_t disk = fs.numDisks(); } Directory tmpDir(params);
C++对定义于不同编译单元的non-local static对象的初始化相对次序并无明确定义。除非fs在tmpDir之前先被初始化,否则tmpDir的构造函数会用到尚未初始化的fs
class FileSytem {}; FileSytem &fs() { static FileSytem fs; return fs; } class Directory {}; Directory::Directory(params) { size_t disks = fs().numDisks(); } Directory &tmpDir() { static Directory dir; return dir; }
单例模式,函数内的local static对象会在该函数被首次调用时初始化。如果从未调用non-local static对象的仿真函数,就不会引发构造和析构成本
任何一种non-const static对象,无论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这种麻烦的一种做法是:在程序单线程启动阶段手工调用所有reference-return函数,这可消除与初始化有关的竞速形式