在C中,宏可以用来提高效率,宏的实现是用预处理器而不是编译器。预处理器直接用宏代码代替调用,所以就没有了参数压栈、生成汇编语言的CALL、返回参数、执行汇编语言的RETURN的时间花费。所有的工作由预处理器完成,因此,不用花费什么就具有了程序调用的便利和可读性。C++中,使用预处理器宏存在两个问题。第一个问题在C中也存在:宏看起来像一个函数用,但并不总是这样。这就隐藏了难以发现的错误。第二个问题是C++特有的:预处理器不许存取私有( private )数据。这意味着预处理器宏在用作成员函数时变得非常无用。因此,C++用了内联函数。
预处理器的缺陷
示例1:
#define f (x) (x+1);
这里f和(x)之间不小心加了一个空格,因此,使用时f(1)的结果是(x) (x+1)(1),显然不是我们希望看到的。
示例2:
#define floor(x,b) x>=b?0:1;
if(floor(a&0x0f,0x07)) // ...
宏将展开成:
if(a&0x0f>=0x07?0:1)
因为&的优先级比>=低,因此也出现了错误的结果。
示例3:
#define band(x) (((x)>5 && (x)<10) ? (x) : 0)
band(x)很像一个函数了,但是如果这样使用:
int i = 5;
band(++i);
显然也会出现问题。
class X {
int i;
public :
#define val (X::i) //Error
所以如果想效率的存取i,只能把它当作public变量,但这显然又破坏了封装性。
内联函数
在C++中,宏的概念是用内联函数来实现的,而内联函数是真正的函数。唯一不同的是内联函数在适当时像宏一样展开,所以函数调用的开销被消除。因此,最好不使用宏,只使用内联函数。
一般应该把内联定义放在头文件里。当编译器看到这个定义时,它把函数类型(函数名和返回值)和函数体放到符号表里。当使用函数时,编译器确保正确的调用,然后将函数调用替换为函数体,因而消除了开销。内联代码的确占用空间,但假如函数较小,这实际上比为了一个普通函数调用而产生的代码(参数压栈和执行CALL)占用的空间还少。因此,假如函数较大,那么花费在函数体内的时间相对于进出函数的时间的比例就会较大,所以好处会较少,而且函数调用的地方进行了代码复制,造成代码膨胀。所以在函数太复杂时,编译器多数情况下会放弃内联。假如要显式或隐含地取一个函数的地址,编译器也不能对这个函数执行内联,因为这时编译器必须为函数代码分配内存,从而为我们产生一个函数的地址。
为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但在类内部定义内联函数时并不必须加inline,任何在类内部定义的函数自动地为内联函数。
对一个成员变量的存取,就可以用内联函数,一方面节省了函数调用的开销,另一方面代码短小。同时,避免了成员变量的直接访问。
class Forward {
int i;
public:
Forward() : i(0) {}
// Call to undeclared function:
int f() const { return g() + 1; }
int g() const { return i; }
};
以上代码并不会有问题,f中使用了内联函数g,但g在h之后声明,这并没有关系,因为C++规定,非内联函数直到类声明结束才赋值。
预编译宏的优势
预编译宏有3个特别的特征无法被内联替代:字符串定义,字符串串联和标志粘贴。#可以将一个标志符转换成一个字符串。
#define DEBUG(X) cout<<#X " = " << X << endl 可以打印任何变量的值
#define TRACE(S) cout << #S << endl; S 打印它们执行的语句,比如TRACE(f(i)) ;第二句的S是执行这个语句,前面的分号用逗号可以避免一些前面所说的宏定义的问题。
#define FIELD(a) char* a##_string; int a##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
每次调用这个宏,都会产生两个变量:一个字符指针,一个字符串长度。##可以把两个标志符粘贴在一起,生成一个新的标志符。