#define用来定义常量。
首先我们需要知道,#define会在预编译的时候,以字符串替换的形式被替换掉。假设我们#define AspectRatio= 1.635。如果AspectRatio在使用的过程中报错,编译器会直接提示1.635。如果这个宏是其他文件定义的,那么对于使用者而言,他甚至都不知道1.635是个什么东西。
下面还有两种特殊情况
定义常量指针
常量指针通常需要用两个const。即既限定指针指向对象无法改变,也限定无法通过指针改变对象值。因为如果用#define定义指针的话,通常定义的是一个常量地址。这个地址代表的一定是一个固定的东西,因此必须是指针常量。并且,我们当然也不希望通过地址值直接修改任何东西。所以也必须是常量指针。
下面是个例子。
const char* const author_name = "Scott Meyers";
这里面定义的一个指针,就是指针常量。但通常我们用string而非char*
const std::string author_name("Scott Meyers");
class专属常量
类中的常量,我们希望他只有一版,因此需要定义为静态的。
class GamePlayer{
private:
static const int number_turns = 5;
int scores[number_turns];
}
但由于上面的这个常量是写在类中的,因此只是一个声明,而非定义。在取地址的时候,如果这个常量是内置的整数型,int,char,bool,就没什么问题。但是如果是别的类,或者有的其他编译器不允许这样,那就要在外面定义一下。
const int GamePlayer::number_turns = 5;
因为C++要求任何东西都要被定义才能使用。
enum hack
上述问题还有一个解决方法,就是enum hack。枚举型的变量可以当成一个int使用。而且枚举型可能比const更像#define。因为const变量可以取地址,但是enum和#define都无法取地址。
class GamePlayer {
private:
enum {num_turns = 5};
int scores[num_turn];
}
滥用宏
这一块我们讨论一个小问题,就是把宏定义成非常复杂的函数。这样可能导致一些意外的结果。下面是个例子
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b)
由于宏会直接替换,所以最后就成了
f((++a) > (b) ? (++a) : (b))
很显然,我们只希望a自加一次,但是这里却加了两次。
解决这个问题的方法是使用模板内联函数
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b)
}
#define 设计的通常都是些简单的函数,因此完全可以将他们设计成内联函数。最大的好处是,模板内联函数是个函数,他可以放在class当中。这样就可以为它设计访问性,比如public和private。但是宏定义是没有类中权限的。
结论
- 对于单纯常量,用const替换
- 对于形似函数的宏,最好用inline替换