C/C++ 宏定义 (二)

2014-11-23 23:18:22 · 作者: · 浏览: 26
,这就是中的getchar,getchar的确就是个宏,不是函数——虽然它的功能像个函数。)
使用带参数的宏替代实际的函数的优点:
1) 、 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销——存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。
2) 、 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用MAX宏从两个数中选出较大的一个,数的类型可以是int,long int,float,double等等。
但是带参数的宏也有一些缺点。
1) 、 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加(因此编译后的代码变大)。宏使用得越频繁,这种效果就越明显。当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。思考一下,如果我们用MAX宏来找出3个数中最大的数会怎样?
n = MAX(i, MAX(j,k));
下面是预处理后的这条语句:
n=((i)>(((j)>(k) (j):(k))) (i):(((j)>(k) (j):(k))));
2) 、宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们是否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个出错信息。预处理器不会检查宏参数的类型,也不会进行类型转换。
3) 、无法用一个指针来指向一个宏。如在17.7节中将看到的,C语言允许指针指向函数。这一概念在特定的编程条件下非常有用。宏会在预处理过程中被删除,所以不存在类似的“指向宏的指针”。因此,宏不能用于处理这些情况。
4) 、宏可能会不止一次地计算它的参数。函数对它的参数只会计算一次,而宏可能会计算两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。考虑下面的例子,其中MAX的一个参数有副作用:
n = MAX(i++, j);
下面是这条语句在预处理之后的结果:
n =((i++)>(j) (i++):(j));
如果i大于j,那么i可能会被(错误地)增加了两次,同时n可能被赋予了错误的值。
由于多次计算宏的参数而导致的错误可能非常难于发现,因为宏调用和函数调用看起来是一样的。更糟糕的是,这类宏可能在大多数情况下正常工作,仅在特定参数有副作用时失效。为了自保护,最好避免使用带有副作用的参数。
带参数的宏不仅适用于模拟函数调用。他们特别经常被作为模板,来处理我们经常要重复书写的代码段。如果我们已经写烦了语句
printf("%d"\n, x);
因为每次要显示一个整数x都要使用它。我们可以定义下面的宏,使显示整数变得简单些:
#define PRINT_INT(x) printf("%d\n", x)
一旦定义了PRINT_INT,预处理器会将这行
PRINT_INT(i/j);
//转换为
printf("%d\n", i/j);
3. #运算符
宏定义可以包含两个运算符:#和##。编译器不会识别这两种运算符相反,它们会在预处理时被执行。
#运算符将一个宏的参数转换为字符串字面量(字符串字面量(string literal)是指双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个字符),, 简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号. 它仅允许出现在带参数的宏的替换列表中。(一些C程序员将#操作理解为“stringization(字符串化)”;其他人则认为这实在是对英语的滥用。)用比较官方的话说就是将语言符号(Token)转化为字符串。
#运算符有大量的用途,这里只来讨论其中的一种。假设我们决定在调试过程中使用PRINT_INT宏作为一个便捷的方法,来输出一个整型变量或表达式的值。#运算符可以使PRINT_INT为每个输出的值添加标签。下面是改进后的PRINT_INT:
#define PRINT_INT(x) printf(#x " = %d\n", x)
x之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串字面量。因此,调用
PRINT_INT(i/j);
//会变为
printf("i/j" " = %d\n", i/j);
C语言中相邻的字符串字面量会被合并,因此上边的语句等价于:
printf("i/j = %d\n", i/j);
当程序执行时,printf函数会同时显示表达式i/j和它的值。例如,如果i是11,j是2的话,输出为
i/j = 5
TIPI例子:
#define STR(x) #x
int main(int argc char** argv)
{
printf("%s\n", STR(It's a long string)); // 输出 It's a long str
return 0;
}
4. ##运算符
在C语言的宏中,"##"被称为 连接符(concatenator),它是一种预处理运算符, 用来把两个语言符号(Token)组合成单个语言符号。 这里的语言符号不一定是宏的变量。并且双井号不能作为第一个或最后一个元素存在.
##运算符可以将两个记号(例如标识符)“粘”在一起,成为一个记号。(无需惊讶,##运算符被称为“记号粘合”。)如果其中一个操作数是宏参数,“粘合”会在当形式参数被相应的实际参数替换后发生。考虑下面的宏:
如下例子:当MK_ID被调用时(比如MK_ID(1)),预处理器首先使用自变量(这个例子中是1)替换参数n。接着,预处理器将i和1连接成为一个记号(i1)。下面的声明使用MK_ID创建了3个标识符:
#define MK_ID(n) i##n
int MK_ID(1), MK_ID(2), MK_ID(3);
//预处理后声明变为:
int i1, i2, i3;
##运算符不属于预处理器经常使用的特性。实际上,想找到一些使用它的情况是比较困难的。为了找到一个有实际意义的##的应用,我们来重新思考前面提到过的MAX宏。如我们所见,当MAX的参数有副作用时会无法正常工作。一种解决方法是用MAX宏来写一个max函数。遗憾的是,往往一个max函数是不够的。我们可能需要一个实际参数是int值的max函数,还需要参数为float值的max函数,等等。除了实际参数的类型和返回值