c语言使用单链表写队列ADT,C语言之在#define中使用参数-诺禾

在#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中,如下图所示。

C语言之在#define中使用参数

Parts of a function-like macro definition

下面是一个类函数宏的示例:

#define SQUARE(X) X*X

在程序中可以这样用:

z = SQUARE(2);

这看上去像函数调用,但是它的行为和函数调用完全不同。程序macarg.c 演示了类函数宏和另一个宏的用法。该示例中有一些陷阱,请读者仔细阅读序。

The macarg.c Program

/mac_arg.c -- macros with arguments/

#include #define SQUARE(X) X*X

#define PR(X) printf("The result is %d.n", X)

int main(void)

{

int x = 5;

int z;

printf("x = %dn", x);

z = SQUARE(x);

printf("Evaluating SQUARE(x): ");

PR(z);

z = SQUARE(2);

printf("Evaluating SQUARE(2): ");

PR(z);

printf("Evaluating SQUARE(x+2): ");

PR(SQUARE(x+2));

printf("Evaluating 100/SQUARE(2): ");

PR(100/SQUARE(2));

printf("x is %d.n", x);

printf("Evaluating SQUARE(++x): ");

PR(SQUARE(++x));

printf("After incrementing, x is %x.n", x);

return 0;

}

SQUARE宏的定义如下:

#define SQUARE(X) XX

这里,SQUARE是宏标识符,SQUARE(X)中的X是宏参数,XX是替换列表。程序清单16.2中出现SQUARE(X)的地方都会被XX替换。这与前面的示例不同,使用该宏时,既可以用X,也可以用其他符号。宏定义中的X由宏调用中的符号代替。因此,SQUARE(2)替换为22,X实际上起到参数的作用。 然而,稍后你将看到,宏参数与函数参数不完全相同。下面是程序的输出。注意有些内容可能与我们的预期不符。实际上,你的编译器输出甚至与下面的结果完全不同。

x = 5 Evaluating SQUARE(x): The result is 25. Evaluating SQUARE(2): The result is 4. Evaluating SQUARE(x+2): The result is 17. Evaluating 100/SQUARE(2): The result is 100. x is 5. Evaluating SQUARE(++x): The result is 42. After incrementing, x is 7.

前两行与预期相符,但是接下来的结果有点奇怪。程序中设置x的值为5,你可能认为SQUARE(x+2)应该是77,即49。但是,输出的结果是17,这不是一个平方值!导致这样结果的原因是,我们前面提到过,预处理器不做计算、不求值,只替换字符序列。预处理器把出现x的地方都替换成x+2。因此,xx变成了x+2x+2。如果x为5,那么该表达式的值为: xx 变成了 x+2*x+2。

如果x为5,那么该表达式的值为:

5+2*5+2 = 5 + 10 + 2 = 17

该例演示了函数调用和宏调用的重要区别。函数调用在程序运行时把参数的值传递给函数。宏调用在编译之前把参数记号传递给程序。这两个不同的过程发生在不同时期。是否可以修改宏定义让SQUARE(x+2)得36?当然可以,要多加几个圆括号:

#define SQUARE(x) (x)(x)

现在SQUARE(x+2)变成了(x+2)(x+2),在替换字符串中使用圆括号就得到符合预期的乘法运算。但是,这并未解决所有的问题。下面的输出行:

100/SQUARE(2)

将变成:

100/22

根据优先级规则,从左往右对表达式求值:(100/2)2,即50*2,得100。把SQUARE(x)定义为下面的形式可以解决这种混乱:

#define SQUARE(x) (xx)

这样修改定义后得100/(22),即100/4,得25。要处理前面的两种情况,要这样定义:

#define SQUARE(x) ((x)(x))

因此,必要时要使用足够多的圆括号来确保运算和结合的正确顺序。尽管如此,这样做还是无法避免程序中最后一种情况的问题。SQUARE(++x)变成了++x++x,递增了两次x,一次在乘法运算之前,一次在乘法运算之后:

SQUARE(++x)

变成了:

++x*++x

递增了两次x,一次在乘法运算之前,一次在乘法运算之后:

++x++x = 67 = 42

由于标准并未对这类运算规定顺序,所以有些编译器得76。而有些编译器可能在乘法运算之前已经递增了x,所以77得49。在C标准中,对该表达式求值的这种情况称为未定义行为。无论哪种情况,x的开始值都是5,虽然从代码上看只递增了一次,但是x的最终值是7。解决这个问题最简单的方法是,避免用++x作为宏参数。一般而言,不要在宏中使用递增或递减运算符。但是,++x可作为函数参数,因为编译器会对++x求值得5后,再把5传递给函数。

1 用宏参数创建字符串:#运算符

下面是一个类函数宏:

#define PSQR(X) printf("The square of X is %d.n", ((X)*(X)));

假设这样使用宏:

PSQR(8);

输出为:

The square of X is 64.

注意双引号字符串中的X被视为普通文本,而不是一个可被替换的记号。C允许在字符串中包含宏参数。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换成字符串。例如,如果x是一个宏形参,那么#x就是转换为字符串"x"的形参名。这个过程称为字符串化(stringizing)。程序清单16.3演示了该过程的用法。

Listing 16.3 The subst.c Program

/subst.c -- substitute in string/

#include #define PSQR(x) printf("The square of " #x " is %d.n",((x)*(x)))

int main(void)

{

int y = 5;

PSQR(y);

PSQR(2 + 4);

return 0;

}

该程序的输出如下:

The square of y is 25. The square of 2 + 4 is 36.

调用第1个宏时,用"y"替换#x。调用第2个宏时,用"2+4"替换#x。ANSI C字符串的串联特性将这些字符串与printf()语句的其他字符串组合,生成最终的字符串。例如,第1次调用变成:

printf("The square of " "y" " is %d.n",((y)*(y)));

然后,字符串串联功能将这3个相邻的字符串组合成一个字符串:

"The square of y is %d.n"

2 预处理器黏合剂:##运算符

与#运算符类似,##运算符可用于类函数宏的替换部分。而且,##还可用于对象宏的替换部分。##运算符把两个记号组合成一个记号。例如,可以这样做:

#define XNAME(n) x ## n

然后,宏XNAME(4)将展开为x4。程序清单16.4演示了##作为记号粘合剂的用法。

Listing 16.4 The glue.c Program

// glue.c -- use the ## operator

#include #define XNAME(n) x ## n

#define PRINT_XN(n) printf("x" #n " = %dn", x ## n);

int main(void)

{

int XNAME(1) = 14; // becomes int x1 = 14;

int XNAME(2) = 20; // becomes int x2 = 20;

int x3 = 30;

PRINT_XN(1); // becomes printf("x1 = %dn", x1);

PRINT_XN(2); // becomes printf("x2 = %dn", x2);

PRINT_XN(3); // becomes printf("x3 = %dn", x3);

return 0;

}

该程序的输出如下:

x1 = 14

x2 = 20

x3 = 30

注意,PRINTXN()宏用#运算符组合字符串,##运算符把记号组合为一个新的标识符。

3 变参宏:…和VAARGS

一些函数(如printf())接受数量可变的参数。stdvar.h头文件(本章后面介绍)提供了工具,让用户自定义带可变参数的函数。C99/C11也对宏提供了这样的工具。虽然标准中未使用“可变”(variadic)这个词,但是它已成为描述这种工具的通用词(虽然,C标准的索引添加了字符串化(stringizing)词条,但是,标准并未把固定参数的函数或宏称为固定函数和不变宏)。通过把宏参数列表中最后的参数写成省略号(即,3个点…)来实现这一功能。这样,预定义宏 _ VAARGS 可用在替换部分中,表明省略号代表什么。例如,下面的定义:

#define PR(...) printf(__VA_ARGS__)

假设稍后调用该宏:

PR("Howdy");

PR("weight = %d, shipping = $%.2fn", wt, sp);

对于第1次调用,_ VAARGS 展开为1个参数:"Howdy"。对于第2次调用, VAARGS 展开为3个参数:

"weight = %d, shipping = $%.2fn", wt, sp

因此,展开后的代码是:

printf("Howdy");

printf("weight = %d, shipping = $%.2fn", wt, sp);

程序variadic.c演示了一个示例,该程序使用了字符串的串联功能和#运算符。

// variadic.c -- variadic macros

#include #include #define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)

int main(void)

{

double x = 48;

double y;

y = sqrt(x);

PR(1, "x = %gn", x);

PR(2, "x = %.2f, y = %.4fn", x, y);

return 0;

}

第1个宏调用,X的值是1,所以#X变成"1"。展开后成为:

print("Message " "1" ": " "x = %gn", x);

然后,串联4个字符,把调用简化为:

print("Message 1: x = %gn", x);

下面是该程序的输出:

Message 1: x = 48

Message 2: x = 48.00, y = 6.9282

记住,省略号只能代替最后的宏参数:

#define WRONG(X, ..., Y) #X #__VA_ARGS__ #y // won't work

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值