这个条款的本质:在编程过程中多用编译器,少用预处理器。因为#define是宏定义,他不被视作语言的一部分。而const,enums,inlines对应着3种不同的情况。
举个栗子:
#define PI 3.14159
这段代码在程序预处理过程中就执行了,这个记号名称PI,可能没有进入记号表,也从未被编译器看见。这样的话有一个不好的后果和一个潜在的风险。不好的后果就是如果程序编译报错,因为记号表没有收录这个记号名称,他会抱3.14159出错,会给后期的排查带来很大的困难;潜在的风险就是,他会直接替换,而不会对类型进行检查。
针对记号名称没有出现在记号表有一个解决办法:
//以一个常量替换上述的宏
const double PI = 3.14159;
除了以上的常量替换,有2种情况相对来说特殊一些:
(1)定义常量指针
简而言之,有必要将指针申明为const;
(2)class专属常量
class相较于struct更加强调作用域,#defines不能满足这个要求,而且不能提供任何封装性,所以对于有些专属常量,谨慎使用#defines。
class GamePlayer{
private:
static const int NumTurns = 5; //声明式
int scores[NumTurns];
//to do sth;
//高级编译器支持
}
如果程序中需要对class内的专属常量进行取地址的操作,需要另外提供定义式
const int GamePlayer::NumTurns; //定义式
旧式的编译器不支持在变量声明时获得初始值,所以按照一贯的习惯(变量在头文件声明,在实现文件中定义)。
class CostEstimate {
static const double PI; //常量声明
//to do sth; //位于头文件
};
const double
CostEstimate::PI = 3.14159; //常量定义
//位于实现文件内
但是如果遇到上面的例子,你要声明一个数组,编译器坚持要知道数组的大小。这时候就可以使用“the enum hack"补偿做法。
一个属于枚举类型的数值,可以权充ints使用。
class GamePlayer{
private:
enum{NumTurns = 5};
int scores[NumTurns];
...
}
另一种情况就是实现宏,使用宏的时候,尤其要注意,表达式外要加上足够多的括号,不然,会有一些意想不到的错误。
举个栗子:
//a,b中的较大值给到f
#define CALL_WITH_MAX(a,b) f((a) >(b) ?(a) :(b))
使用这个宏定义
int a = 5,b = 0;
CALL_WITH_MAX(++a,b); //a累加2次
CALL_WITH_MAX(++a,b+10); //a累加1次
++的次数取决与和谁比较,这样的宏定义,就有很多隐藏的风险。因为宏不会像函数调用那样产生额外的开销,但与此同时会有不可预料的行为以及类型不安全的情况发生。
C++给出的解决方法是:templete inline函数
templete<typename T>
inline void callwithMax(const T&a,const T&b){
f(a > b ? a: b);
}
但是#define在现阶段是无法被代替的,要充分了解他,并知悉他潜在的风险。
书山有路勤为径,学海无涯苦作舟。
《Effective C++》