条款02 对于单纯常量,最好以const对象或enum替换#define;对于形似函数的宏,最好改用inline函数替换#define。
1首先了解一下预处理和编译的区别:
编译程序对源代码进行编译之前,即在语法分析、代码生成和优化之前,由预处理程序对源代码进行第一次处理。处理时,它忽略注释语句,加入.h头文件,并按定义进行替换。预处理的输出,即是编译程序的输入。预处理命令必须单行书写,不以分号 " ;" 结束。
编译预处理包括: 宏定义;条件编译; 文件包含。
#define就是定义预编译时候处理的宏。由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。但是#define只进行简单的字符替换,无类型检测。
#define例:#define DEFINNUMBER 1.653
2接下来分析条款02执行的原因:
(1)如果适用#define 在编译器处理代码之前有可能已被预处理器移走,所使用的名称并未进入记号表(symbolic table)。如上例,预处理器将宏替换为值1.653,导致记号名称DEFINNUMBER无法进入记号表。
(2)常量比#defile导致较小的码。预处理器盲目的将宏替换为值,导致目标码中出现多处值。
(3)#define不能够定义class专属常量,也不能提供任何封装性。
(4)预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受C++严格类型检查的好处,从而可能成为引发一系列错误的隐患。
const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
3 下面说说以const对象或enum替换#define的情况
3.1 以const替换#define的场合:
const DataType VariableName = VariableValue ;
3.2 class专属常量的场合:
class ClassA{
private:
static const int number = 5;//声明式
int scores[number]; //使用
}
const int ClassA::number; //定义式
声明式中使用static:确保只能有一份实体;
in-class初值设定只对int型有效,其他类型不能在声明式中赋初值,可放在定义式中。旧编译器不支持该声明式的赋初值,这种情况下可以采用所谓的“the enum hack补偿做法”,如下:
class ClassA{
private:
enum{number=5};//让number成为5的一个记号名称
int scores[number];
}
enum hack的行为某方面像是#define 例如取enums和#define的地址都是不合法的,去const的地址是合法的。如果你不想让人获得一个指针或者引用只想某个常量,可以采用enum。
4 接下来是用inline函数替换#define的情况
inline相比#define的优势:
表达式形式的宏定义一例:
#define ExpressionName(Var1,Var2) (Var1+Var2)*(Var1-Var2)
这种表达式形式宏形式与作用跟函数类似,但它使用预编译器,没有堆栈,使用上比函数高效。但它只是预编译器上符号表的简单替换,不能进行参数有效性检测及使用C++类的成员访问控制。
如下:
#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被累加2次
CALL_WITH_MAX(a,b+10); //a被累加一次
#define a的递增次数取决于比较值的大小,行为不可预料。
inline代码放入预编译器符号表中,高效;它是个真正的函数,调用时有严格的参数检测;它也可作为类的成员函数。如下定义:
template<typename T>
inline void CALL_WITH_MAX(const T& a,const T& b){
f(a>b?a:b);
}
inline相比普通函数的优势:
调用函数比求解等价表达式要慢的多,函数在调用前需要先保存寄存器并在返回时恢复;复制实参;程序还需要转向一个新位置执行。
内联函数在编译时候被内联的展开,避免函数调用的开销。
*内联函数应该在头文件中定义,在头文件中修改或加入了内联函数后,使用该头文件的所有源文件都必须重新编译。
综上所述,使用inline函数不仅能获得宏带来的效率,也同时具有函数的可预料性和类型安全性。