01 将C++看做一个语言联邦
虽然C++强大且高效,但在使用时却经常有“例外情况”发生,以至于我们不明白C++到底在做什么或是该怎样做。最简单的方法就是不把C++作为一个单独的语言,而是很多种不同语言的结合体。在使用这些语言时,我们遵守他们各自的规则 。而这些分语言的规则往往是通俗易懂且容易记忆的。
我们可以把C++看做四种次语言的结合体。
1、C
- C++是以C为基础的
- 区块、语句、预处理器、内置数据类型、数组、指针都来自C
- 当用C成分工作时,没有模板,没有异常,没用重载...
2、Object-Oriented C++
- 面向对象的特性
- classes(包括构造函数、析构函数)、封装、继承、多态、虚函数...
3、Template C++
- 泛型编程部分
- 在编程时考虑模板与相应的编程条款
- TMP(模板元编程)
4、STL
- 强大的标准库
- 容器、迭代器、算法、函数对象的规约
这四种次语言拥有自己不同的守则和公约。分别熟悉他们后对于C++的理解和使用就会容易许多。
02 尽量以const、enum、inline替代#define
因为预处理器的机制,当我们使用#define时,很多情况下会导致莫名其妙的问题。因为#define并不被视为语言的一部分,而是在预处理过程中被替换掉。下面讨论三种情况。
1、以const替代#define
假设有这样一条语句
#define ASPECT_RATIO 1.653
虽然在大学上C语言课时老师经常会要求我们这样做,但这很可能导致调试时的错乱分析。
在这条语句里,ASPECT_RATIO可能从未被编译器看到,它在预处理阶段就被替换掉了。当我们使用这个常量但获得一个错误信息时,很可能会看到错误信息里包含1.653而不是它的名称。如果这个宏定义发生在并非由你写的头文件中,你可能会感到很困惑并且花费大量时间追踪它来自哪里。
为了解决这个问题,我们最好把使用的名称放入记号表。
下面是一个解决方案
const double AspectRatio = 1.653
很明显编译器会看到AspectRatio并且记入符号表内。同时,这样做可能会减少代码量:如果使用宏定义很可能目标码中会出现多份1.653,但改成常量后就不会出现。
当然,任何事情都有例外(尤其是当你使用C++时)
第一个:常量指针
因为常量定义一般都被放在头文件中,所以当使用指针时要将其声明为const。当我们定义一个常量字符串时,我们要写const两次:
const char* const authorName = "Scott Meyers";
而且,在真正使用C++时,string对象往往比它的前辈char*-based更适宜。所以这样定义一般更好。
const std::string authorName("Scott Meyers");
第二个:class专属常量
为了限制常量的作用域,需要让常量成为class的成员。而为了确保常量至多只有一份实体,我们要让它成为一个static成员。
class GamePlayer
{
private:
static const int NumTurns = 5; //常量声明式
int scores[NumTurns];
...
};
类内的静态成员需要一个类外的定义式,所以要提供额外的定义式
const int GamePlayer::NUmTurns;
2、以enum替代#define
由于class常量已经在声明的时候获得了初值,所以在定义时不可以再设置初始值。
某些老旧的编译器可能并不支持声明时获得初值这种语法,所以我们要在定义式时设置初值。但是还有一些编译器坚持不允许static整型class常量完成上面的数组初始化。可以采用枚举类型来解决:
class GamePlayer
{
private:
enum{ NumTurns = 5};
int scores[NumTurns];
...
};
其实enum更像是#define而不是const:例如取一个const的地址是合法的,而取enum的地址不合法。如果不想让别人通过一个指针来获取某个整数常量,可以使用enum。
许多代码用到了enum hack,它是模板元编程的基础部分,所以还是有必要了解它的使用的。
3、以inline替代#define
看下面这个宏:
#define CALL_WITH_MAX(a,b) f((a) > (b) > (a) : (b))
这个宏定义看上去就给人一种很糟糕的感觉。当我们遇到这种情况时,使用一个template inline函数是更好的:
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
这是一个真正的函数,所以它遵守作用域和访问规则。
总结:
- 对于单纯常量,最好用const对象或enums替换#define
- 对于类似函数的宏,最好用inline函数替换#define