分类: C/C++
2008-05-31 08:31:14
当你调用一个过程既被声明成函数又被声明成宏时,你可以用如下两种方法来强制编译器使用函数或宏(编译器默认会使用宏)。
1、使用强制类型转换
#include
a = toupper(a); //使用宏
a = (toupper)(a); //使用函数,因为toupper被强制转换成函数指针。
2、使用#undef
#include
#undef toupper //注意这里是小写,以后调用过程toupper都为函数调用,因为宏定义已被//取消。
那么,我们应该怎样来选择在一个程序中使用宏还是使用函数调用呢?
1、时间VS 大小
使用宏时,因为宏被就地展开,因此,用宏编译出来的代码会比用函数编译出来的代码大。但宏展开后没有了函数的参数传递,所以运行起来比函数要快。
使用函数时因函数只是被多次执行,因此代码小,但函数调用时增加了开销。
2、函数可以通过函数指针来调用,而宏则不行。
如:
int AddOne(int a) {++a}; //定义一个函数
int (*plusOne)(int a); //定义一个函数指针
pluseOne = Addone; //初始化函数指针
(*pluseOne)(2); //通过函数指针调用函数
3、在多任务并发执行的应用程序中,使用函数时需考虑函数的重入问题。宏因为就地展开,因此,不用考虑重入问题。
4、使用宏时,要注意宏的参数有可能出错。
a) 宏定义不当出错
如:
宏定义
#define F (x) (x+1)
现在假如有一个如下所示的宏调用:
F(1)
预处理器展开它,出现下面不希望的情况:
(x) (x+1) (1)
出现这个问题是因为在宏定义中F和括号之间存在空格,当这个空格取消后,调用宏时可以有空格空隙,像下面的调用:
F (1)
依然可以正确地展开为:
(1+1)
b) 当宏传递参数时出错
如:优先级不同出错
#define FLOOR(x,b) x>=b ? 0:1
[Page]
现在假如用表达式作参数:
if(FLOOR(a&0x0f,0x07))
宏将展开成:
if(a&0x0f>=0x07 ? 0 : 1)
因为&的优先级比>=的低,所以宏的展开结果将会使我们惊讶。一旦发现这个问题,可以通过在宏定义内的各个地方使用括弧来解决,(这是创建预处理器宏时使用的好方法。)上面的定义可改写成如下:
#define FLOOR(x,b) ((x)>=(b) ? 0 : 1)
c) 当一个宏多次使用参数时出错
如:
#define toupper(c) ((islower(c)) ? _toupper(c) : (c))
#include
int a = ‘m’;
a = toupper(a++);
程序运行后,a的值会加2;
上面的程序被编译器展开后:
int a = ‘m’;
a = islower(a++) ? _toupper(a++) : (a++);
由此可见,变量a被增加了两次。
5、类型检查
当定义为函数时,编译器会检查函数的参数,但定义为宏时,编译器只检查参数的个数。
6、使用内联函数(C++或支技内联函数的编译器中)
[NextPage]
编译器会根据内联函数的执行时间和代码长度来决定是展开内联函数还是将内联函数作为普通函数调用。(如果内联函数内部有循环,则编译器会把内联函数当普通函数调用,因为循环执行的时间一般很长,相对于函数调用的时间来说,就地展开并不会提高代码的效率。同样,当函数较大时,由于需要在函数调用处的每一处重复复制代码,这样将使代码膨胀,在速度方面获得的好处就会减少,编译器同样不会展开这种内联函数。)
A) 宏不能定义在类里:
如不能有如下或近似的定义
class X{
int i;
public:
#define VAL(x::i) //Error
B) 内联函数的定义:
任何在类中定义的函数自动地成为内联函数,但也可以在非类的函数前面加上inline关键字使之成为内联函数,但为了使之有效,必须使函数体和声明结合在一起,否则,编译器将它作为普通函数对待。因此
inline int plusOne(int x);
没有任何效果,仅仅只是声明函数(这不会在稍后某个时候得到一个内联定义)正确的定义方法如下:
inline int plusOne(int x) {return ++x};
注意,编译器将检查函数参数列表是否正确,并返回值(进行必要的转换)。这些事情是预处理器无法完成的。
C)
常量跟宏定义
预处理器在C语言中用名字替代值的典型用法是这样的:
#define BUFSIZE 100
大多数情况,BUFSIZE的工作方式与普通变量类似;而且没有类型信息。这就会隐藏一些很难发现的错误。我们可以用const 常量来替代它。
const int bufsize = 100 ;
这样就可以在编译时编译器需要知道这个值的任何地方使用bufsize,同时编译器还可以执行常量折叠(constant folding),也就是说,编译器在编译时可以通过必要的计算把一个复杂的常量表达式通过缩减简单化。这一点在数组定义里显得尤为重要:
[Page]
char buf[bufsize];
指向const的指针(指针指向的内容不能改变)
const int* u;
int const * u;
const 指针(指针不能改变)
int d=1;
int * const w = &d;