在C中,保持效率的一个方法是使用宏。宏可以不要普通的函数调用代价就可使之看起来像函数调用。宏的实现是用预处理器而不是编译器。预处理器直接用宏代码代替宏调用,所以就没有了参数压栈、生成汇编语言的CALL、返回参数、执行汇编语言的RETURN等的开销。所有的工作由预处理器来完成,因此不用花费什么就具有了程序调用的便利和可读性。
在C++中,使用预处理器宏存在两个问题。第一个问题在C中也存在:宏看起来像一个函数调用,但并不总是这样。这样就隐藏了难以发现的错误。第二个问题是C++特有的:预处理器不允许访问类的成员数据。这意味着与处理器宏不能用作类的成员函数。
为了既保持预处理器宏的效率又增加安全性,而且还能像一般成员函数一样可以在类里访问自如,C++引入了内联函数。
预处理器的缺陷 |
这里存在两个问题。
第一个问题是表达式在宏内展开,所以它们的优先级不同于所期望的优先级,例如:
#define FLOOR(x,b) x >= b ? 0:1
现在假如用表达式作参数:
if(FLOOR(a&0x0f,0x07)) // ...
宏将展开成:
if(a&0x0f >= 0x07 ? 0:1)
因为&的优先级比>=的低,所以宏的展开结果将会使我们惊讶。一旦发现这个问题,可以通过在宏定义内的各个地方使用括弧来解决。上面的定义可改写成如下:
#define FLOOR(x,b) ((x) >= (b) ? 0:1)
前面的问题可以通过谨慎地编程来解决:在宏中将所有的内容都用括号括起来。第二个问题则复杂一些。不像普通函数,每次在宏中使用一个参数,都对这个参数求值。
例如,下面这个宏决定它的参数是否在一定范围:
#define BAND(x) (((x) > 5 && (x) < 10) ? (x) : 0)
只要使用一个“普通”参数,宏和真的函数的工作方式非常相似。但下面的问题出现了:
for(int i = 4;i < 11;i++) { int a = i; out << “a = “ << a << endl << ‘ ’; out << “BAND(++a) = “ << BAND(++a) << endl; out << “ a = “ << a << endl; }
运行结果并不是预期想要得到的。所以像这样的问题可能隐藏了一些难以发现的错误。
内联函数 |
内联函数能够像普通函数一样具有我们所有期望的任何行为。惟一不同之处是内联函数在适当的地方像宏一样展开,所以不需要函数调用的开销。
任何在类中定义的函数自动地成为内联函数,但也可以在非类的函数前面加上inline关键字使之成为内联函数。但为了使之有效,必须使函数体和声明结合在一起,否则,编译器将它作为普通函数对待。因此
inline int plusOne(int x);
没有任何效果,仅仅只是声明函数,成功的方法如下:
inline int plusOne(int x) { return ++x; }
一般应该把内联定义放在头文件里。
为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但这在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动地成为内联函数。
但应注意,使用内联函数的目的是减少函数调用的开销。但是,假如函数较大,由于需要在调用函数的每一处重复复制代码,这样将使代码膨胀,在速度方面获得的好处就会减少。
限制
有两种编译器不能执行内联的情况。在这些情况下,它就像对非内联函数一样。
假如函数太复杂,编译器将不能执行内联。
假如要显式地或隐式地取函数地址,编译器也不能执行内联,因为这时编译器必须为函数代码分配内存从而产生一个函数的地址。