前言
本系列博客将详细学习并介绍《Effective C++ 改善程序与设计的55个具体做法》(第三版)中的各个条款。
本节介绍条款02:尽量以const,enum,inline替换#define!
一、概念
这个条款简单点就是说:尽可能少依赖预处理器,尽量减少#define这种宏定义的使用。
#define可能会导致一些奇奇怪怪的问题(虽然我暂时还没遇到过)。
二、const
省流:用常量代替#define
像以下这种尽可能少用,这种写法可能不会被编译器注意到,这样一来可能ASPECT_RATIO 没进入符号表,那么当出现编译错误时,错误信息可能会提示1.653而不是ASPECT_RATIO ,然后我们还得找1.653是啥,很浪费时间!
#define ASPECT_RATIO 1.653
解决方案:用以下代码去写,用常量去替换,这样的好处一来,这样写肯定会被编译器注意到,二来,#define的写法会让预处理器将宏名称ASPECT_RATIO 替换成1.653会让目标码(object code)多出现一份1.653,而使用从const double的定义方式就不会。注意变量的命名规范和宏不一样!
const double AspectRatio = 1.653;
注意以下两种特殊情况:
1.定义常量指针
先区分一下常量指针(const int * p)和指针常量(int * const p):
常量指针:指向“常量”的指针。常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”。在常量指针中,指针指向的内容是不可改变的,指针看起来好像指向了一个常量。指针初始化后可以修改其指向,但是无法修改对象的值。
int a=10,b=20;
const int *p = &a;
p = &b; //允许修改指向
*p = 15; //报错,不允许修改值
指针常量:指针类型的常量。本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化。指针一旦初始化不可再更改,但是其指向对象的值是可变的。
int a=10,b=20;
int* const p = &a;
*p = 15; //允许修改值
cout<<*p;
p = &b; //报错,read-only variable 'p'
回到正题:这里我们在定义常量指针的时候,由于常量定义式通常放在头文件内,所以要将指针(而不是指针指向的东西)置为const,也就是下面这样:
const char* const authorName = "Scott Meyers";
const std::string authoeName("Scott Meyers"); // 最好写成这样
2.class专属常量
另一个注意的点是class专属常量。为了将常量的作用域限制在类内就必须让它成为类中的一个成员,同时为了确保此常量至多只有一份实体,就必须让它成为一个static成员,如下面代码所示:
class GamePlayer{
private:
static const int NumTurns = 5; // 此处为常量声明式
int score[NumTurns];
};
这里有一处不同,正常的static const int NumTurns = 5;这种写法属于定义式,但在这里则是声明式。C++会要求所使用的任何东西提供定义式,而如果是如上情况(它是class专属常量又是static且为整数类型比如int,char,bool)则需要特殊处理。只要我们不取它们的地址,像上面这样写就行了,但如果需要取某个class专属常量的地址,或者某些编译器必须要定义式,那么就需要在一个实现文件(不是头文件)中另外提供定义式,像这样:
const int GamePlayer::NumTurns;
三、enum
接着第二章的尾巴说,如果编译器不允许“static 整数型 class 常量’’完成“in class 处置设定”,那么就可以用枚举代替,像这样:
class GamePlayer{
private:
enum {NumTurns = 5}; // 此处为常量声明式
int score[NumTurns];
};
这里能用枚举但不能用#define,因为#define没有作用域的概念,一旦宏被定义,除非遇到#undef,否则它就在其后的编译过程中有效,因此#define无法用来定义class专属常量,使用enum就可以解决,特别是有时候不像取地址,那么对enum和#define去取地址都不合法,enum便能完美取代#define。
四、inline
对于实现函数操作的宏,一般需要给所有实参加上小括号,否则容易出问题,但即使加了,也同样完全避免问题,比如下面的例子:
#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被累加一次
调用f之前,a的递增次数取决于“它被拿来和谁比较”,这应该是受了编译过程中有什么不得不做的操作影响,就会带来一些麻烦,此时像这种可能会被频繁调用的小函数就可以用内联函数替代,像下面这样:
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b); // 详见条款20
}
总结
本条款想表达的核心思想就是:当前预处理器虽然还不到退休的时候,但我们要尽可能避免使用它,尽量用const,enum,inline去替代预处理。
日拱一卒,功不唐捐!