1 让自己习惯 C++
条款 01:视 C++ 为一个语言联邦
C++ 可以认为由 4 个次级语言组合而成:C 是 C++ 的语法基础;Object-oriented C++ 实现面向对象设计;Template C++ 实现泛型编程;STL 提供容器、迭代器、算法等。
每个次级语言都有自己的规约,为了使效率尽可能提高,需要遵守每个部分的规约。
条款 02:尽可能以 const, enum, inline
替换 #define
使用 #define
预定义变量将导致无法跟踪错误来源,而且变量会在替换过程中被复制多份。
定义常量指针时需要写两次 const
,保证指针指向的内存空间数值不被更改,同时保证指针指向的内存空间不被修改。
class
中的专属常量需要在声明时加上 static
关键字以保证常量只有一份实体。
如果 class
中专属常量为整数类型,则可以在声明时定义,否则需要额外定义,比如
// Header file
class Example {
static const int m = 5;
// use enum { m = 5 }; if not allowed
static const double n;
}
// Implement file
const double Example::n = 5.0;
如果编译器不允许在声明时定义整型变量,则可以使用 enum
充当整型。
使用 template inline
函数代替 #define
宏函数,即使给每个变量加上括号,宏函数仍然有可能出现错误。
// Don't
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
// Do
template<typeName T>
inline void call_with_max(const T &a, const T &b) {
f(a > b ? a : b);
}
对于简单变量,用 const
对象或 enum
替换 #define
;对于宏函数,用 inline
函数替换 #define
。
条款 03:尽可能使用 const
如果 const
出现在 *
左边,则变量不变;如果 const
出现在 *
右边,则指针指向不变。
void f1(const Widget* pw);
和 void f2(Widget const * pw);
的含义相同。
声明迭代器 iterator
为 const
等同于声明 T* const
指针,此时可以修改指针指向的值但不可修改指针指向的内存空间。如果不希望迭代器修改值,即模拟成 const T*
指针,则需要声明 const_iterator
。
函数返回常量值可以避免一些因为赋值产生的错误。
将成员函数设置为 const
后它不可以修改成员变量,它可以使接口更容易理解,也可以提升运行效率。
如果两个成员函数只是 const
和非 const
的区别,它们可以被重载,比如
class TextBlock {
public:
const char& operator[] (std::size_t position) const { return text[position]; }
char& operator[] (std::size_t position) { return text[position]; }
private:
std::string text;
}
const
成员函数确实不允许修改成员变量,但当成员为指针时,不修改指针的指向,但可以修改指针所指内存空间的值。
如果有必要在 const
成员函数中修改成员变量的值,则需要声明成员变量为 mutable
。
可以通过转型来避免写两个函数体相同但 const
修饰不同的函数,以上面的函数为例
class TextBlock {
public:
const char& operator[] (std::size_t position) const {
...
return text[position];
}
char& operator[] (std::size_t position) {
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[position]
)
}
private:
std::string text;
}
static_cast
将普通对象转换为 const
对象,再由 const_cast
移除 const
。
const
函数调用非 const
函数是一种错误行为,因为对象有可能被调用的非 const
函数修改。
将某些变量、参数、返回类型或成员函数本体声明为 const
可以帮助编译器检测错误;编译器强制要求 const
成员函数不修改成员变量,但可以修改指针成员指向内存空间的值;当 const
和非 const
函数有等价的实现时,可以使非 const
函数调用 const
函数以避免代码重复。
条款 04:确定对象被使用前已先被初始化
读取未初始化的值可能导致程序终止运行或读取到正在使用的对象内存的数值,进而污染该对象导致不确定的结果。
使用正确的方式初始化对象,而不是赋值,比如
class PhoneNumber { ... };
class ABEntry {
public:
ABEntry(const std::string &name, const std::list<PhoneNumber> &phones);
private:
std::string theName;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
}
// Don't
ABEntry::ABEntry(const std::string &name, const std::list<PhoneNumber> &phones) {
theName = name;
thePhones = phones;
numTimesConsulted = 0;
}
// Do
ABEntry::ABEntry(const std::string &name, const std::list<PhoneNumber> &phones)
:theName(name), thePhones(phones), numTimesConsulted(0) { }
// Default
ABEntry::ABEntry()
:theName(), thePhones(), numTimesConsulted(0) { }
赋值方法会先调用 default
构造函数为每个成员变量赋初值,然后再立刻赋予新的值,而初始化方法可以剩下 default
构造函数的操作直接初始化各变量,提高运行效率。这个方法也适用于自己构造 default
构造函数。
成员初始化的顺序总是与类成员变量声明顺序一致,而不是构造函数参数的顺序。
如果成员变量是 const
变量或引用变量,则它们必须被初始化而不能被赋值。
调用其他库中 static
变量时,无法保证该变量已经被正确地初始化,可以使用返回该静态变量的函数代替直接用 extern
变量的方法,类似于单一实例设计模式,避免这个问题。但是任何非 const
的 static
对象在多线程环境下都会导致初始化次序问题,最好的解决办法是在程序的单线程启动阶段手工调用所有返回引用的函数。
对内置类型手工初始化,C++ 不保证使用时已被正确初始化;构造函数使用成员初值列,且排列次序应该与在 class
中的声明顺序一致;用本编译单元中的 static
对象替换非本编译单元的 static
对象。