这个条款或许改为“宁可编译器替换预处理器”比较好。当你做出这样的事:
#define ASPECT_RATIO 1.653
在编译器开始处理源码之前它就被预处理器移走了。于是当你运用此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息也许提到 1.653
而不是 ASPECT_RATIO
。你所使用的名称可能并未进入symbol table/
解决方法是使用一个常量替换上述的宏:
const double AspectRatio = 1.653;
作为一个语言常量,AspectRatio肯定会被编译器看到,当然就会进入symbol table。
当我们以常量替换 #define
,有两种特殊的情况值得说说。第一是定义常量指针。由于常量定义式通常被放在头文件内,因此有必要将指针声明为const。例如若要在头文件内定义一个 const char*
的字符串,你必须写const两次:
const char* const authorName = "Scott Meyers";
string
对象通常比 char*
更适合,所以上述的authorName往往定义称这样更好些:
const std::string authorName("Scott Meyers");
第二个值得注意的是class专属常量。为了将常量的作用域限制在class内,你必须让它成为一个成员;而为确保此常量至多只有一个实例,你必须让它成为一个static成员:
class GamePlayer {
private:
static const int NumTurns = 5; // 常量声明
int scores[NumTurns]; // 使用常量
};
你所看到的是NumTurns的声明式而非定义式。通常C++要求你对所使用的任何东西提供一个定义式,但如果他是个static const成员变量且为整数类型(int, char, bool),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无需提供定义式。但如果你取某个const成员变量的地址,或你的编译器要求看到一个定义式,你就必须另外提供定义式如下:
const int GamePlayer::NumTurns; // NumTurns的定义
把这个式子放进一个实现文件而非头文件。由于该常量已在声明时获得初值,因此定义时不可以再设初值。
请注意,我们无法利用 #define
创建一个const成员变量,因为 #define
并不重视作用域。而const成员变量是可以被封装的。
旧式编译器不支持“in-class初值设定”,你可以将初值放在定义式:
class CostEstimate {
private:
static const double FudgeFactor; // static常量声明,位于头文件
...
};
const double
CostEstimate::FudgeFactor = 1.35; // 定义常量值,位于实现文件
唯一例外是当你在class编译期间需要一个class常量值,例如上述的 GamePlayer::scores
数组的声明中。如果你的编译器不允许static const变量完成 “in-class初始设定”,可改用“the enum hack”补偿做法。其理论基础是“一个属于枚举类型的数值可以被int使用”,于是GamePlayer可定义如下:
class GamePlayer {
private:
enum { NumTurns = 5 }; // the enum hack
int scores[NumTurns];
};
如果不想让别人获得一个指针或引用指向你的某个整数常量,enum可以帮助你实现这个约束。enum和 #define
一样绝不会导致非必要的内存分配。
另一个常见的 #define
误用是以它实现宏。宏看起来像函数,但不会导致函数调用带来的额外开销。下面这个宏带着参数,调用函数f:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
无论如何当你写出这种宏,你必须为所有实参加上小括号,否则可能会有bug。但纵使你为所有实参加上了小括号,看看下面不可思议的事情:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a被累加3次
CALL_WITH_MAX(a++, b+10); // a被累加一次
在这里,调用f之前,a的递增次数竟取决于它被拿来和谁比较!
你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性——只要你写出template inline函数:
template <typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
由于callWithMax是个真正的函数,它遵循作用域和访问规则。你绝对可以写出一个private inline成员函数,一般而言宏无法完成此事。
有了const, enum和inline,我们对预处理器(特别是 #define
)的需求降低了,但并非完全消除。 #include
仍然是必须品,而 #ifdef/#ifndef
也继续扮演控制编译的重要角色。
请记住
- 对于单纯的常量,最好以const对象或enum替换
#define
。- 对于形似函数的宏,最好改用inline函数替换
#define
。