文档章节

第16章 C预处理器和C库 16.3 在#define中使用参数

idreamo
 idreamo
发布于 2017/07/21 05:59
字数 2128
阅读 23
收藏 0

通过使用参数,可以创建外形和作用都与函数相似的类函数宏(function-like macro)

类函数宏的定义中,用圆括号括起来一个或多个参数,随后这些参数出现在替换部分。

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

#define SQUARE(X) X*X

在程序中可以这样使用

z=SQUARE(2);

这看上去就像函数调用,但它们的形为不完全相同。

程序清单16.2  mac_arg.c 程序

/*mac_arg.c --带有参数的宏*/
#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n",X)
int main(void)
{
    int x=4;
    int z;

    printf("x = %d\n",x);
    z = SQUARE(X);
    printf("Evaluating SQUARE(x):");
    PR(Z);
    z = SQUARE(2);
    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 SQUARER(++x):");
    PR(SQUARE(++x));
    printf("After incrementing,x is %x.\n",x);

    return 0;
} 

宏SQUARE的定义为:

#define SQUARE(X) X*X

其中,SQUARE为宏标识符,SQUARE(X)中的X为宏的参数,X*X为替换列表。程序清单16.2中出现SQUARE(x)的地方都用X*X代替。宏定义中的X由程序调用的宏中的符号代替。因此,SQUARE(2)替换为2*2,x实际上起到了参数的作用。

但是,宏的参数与函数的参数不完全一样。下面是程序运行的结果(注意有些答案与我们所期待的不同,实际上您的编译器甚至会给出与下面所示不同的答案):

x = 4
Evaluating SQUARE(X): The result is 16.
Evaluating SQUARE(2): The result is 4.
Evaluating SQUARE(x+2): The result is 14.
Evaluating 100/SQUARE(2): The result is 100.
x is 4
Evaluating SQUARE(++x): The result is 30.
After incrementing,x is 6. 

头两行是我们期待的结果,但接下来的是些奇特的结果。回忆一下,x的值是4.可能您会认为SQUARE(x+2)应该是6*6即36,但输出的结果是14,这不像是个平方值 。产生这种令人费解的结果的简单原因我们曾经声明过:预处理器不进行计算,而只是进行字符串替换。在出现x的地方,预处理器都用字符串x+2进行替换。因此:

x*x  变成  x+2*x+2

如果x为4,表达式的值为:

4+2*4+2=4+8+2=14

本例指出函数调用和宏调用之间的重要差异程序运行时,函数调用把参数的值传递给函数。而编译前,宏调用把参数的语言符号传递给程序。 这是不同时间发生的不同过程。

可以修改定义使SQUARE(X+2)输出36,只需多加几个圆括号:

#define SQUARE(X) (X)*(X)

现在SQUARE(X+2)变成了(X+2)*(X+2),在替换字符串中使用圆括号得到了期待的乘法运算。

但是,这不能解决所有问题:

100/SQRARE(2)

它将变成:

100/2*2  即50*2即100

把SQUARE(X)定义为下面的形式可以解决这种混乱:

#define SQUARE(X) (X*X)

这样做会产生100/(2*2),最后求出值为100/4即25.

要处理前面两个示例中上情况,需要这样定义:

#define SQUARE(X) ((X)*(X))

从中得到的经验是使用必需的足够的圆括号来保证以正确的顺序进行运算和结合。

不过,这些措施还是无法避免最后一个示例中的问题:

SQUARE(++X)

变成:

++x*++x

x进行了两次增量运算,其中一次在乘法操作前,另一次在乘法操作后。

++x*++x=5*6=30

因为对这些运算顺序没有做出规定,所以有些编译器产生乘积6*5.而其他编译器可能在乘法操作前同时对x进行自加操作,从而产生6*6。但在这两种情况下,x的开始值均为4,终止值均为6.然而,从代码来看,x只进行一次增量操作。

解决这个问题的最简单的办法是避免在宏的参数中使用++x。一般来说,在宏中不要使用增量或减量运算符。注意,++x可以作为函数参数,因为会对++x进行计算得到值5,再把5传递给函数。

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

下面是一个类函数宏:

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

注意,引号中的字符串中的X被看作普通文本,而不是被看作一个可被替换的语言符号。

假设您确实希望在字符串中包含宏参数,ANSI C 允许您这么做。在类函数宏的替换部分中,#符号用作一个预处理运算符,它可以把语言符号转化为字符串。例如,如果X是一个宏参量,那么#x可以把参数名转化为相应的字符串。该过程称为字符串化。程序清单16.3说明了该过程。

/*subst.c  --在字符串中进行替换*/
#include <stdio.h>
#define PSQR(x) printf("The square of "#x" is %d.\n",((x)*(x)))

int main(void)
{
    int y = 5;

    PQSR(y);
    PQSR(2+4);
    return 0;
}

输出如下:
The square of y is 25.
The square of 2+4 is 36.

第一次调用宏时,用"y"代替#x;

第二次调用宏时,用“2+4”代替#x。ANSI C 的字符串连接功能将这些字符串与printf()语句中的其他字符串组合以产生最终使用的字符串。例如,第一次调用变成 :

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

接着,字符串的连接功能将这三个相邻的字符串转换为一个字符串:

“The square of y is %d.\n"

16.3.2  预处理器的粘合剂:##运算符

和#运算符一样,##运算符可以用于类函数宏的替换部分。另外,##运算符还可以用于类对象宏的替换部分这个运算符把两个语言符号组合成单个语言符号。例如,可以定义如下宏:

#define XNAME (n) x##n

这样,下面的宏调用:

XNAME(4)

会展开成下列形式:

x4

程序清单16.4用这个宏和另外一个使用##的宏进行了一些语言符号的粘合操作。

程序清单16.4 glue.c程序

// glue.c --使用##运算符
#inclue <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n "=%d\n",x ## n)

int main(void)
{
    int XNAME(1)=14;  //变量int x1=14;
    int XNAME(2)=20;  //变量int x2=20;
    PRINT_XN(1);      //变为printf("x1 = %d\n",x1);
    PRINT_XN(2);      //变为printf("x2 = %d\n",x2);

    return 0;
}

输出如下:
x1 = 14
x2 = 20

    

注意宏PRINT_XN( )如何使用#运算符组合字符串,如何使用##运算符把两个语言符号组合成一个新的标识符。

16.3.3  可变宏:...和_ _VA_ARGS_ _

C99允许可变数量参数的宏。虽然“可变(variadic)不是标准词,但它已经成为标志这种工具的词(虽然”字符串化“和”可变“已经添加到C词汇列表中,但是,固定参数的函数或宏并没有被称为固定(fixadic)函数和不变(normadic)宏)。

实现思想就是宏定义参数列表的最后一个参数为省略号(也就是三个点)。这样,预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,以表明省略号代表什么例如,考虑下面的定义:

#define PR(...) printf(_ _VA_ARGS_ _)

假设稍后用下面的方式调用该宏:

PR("Howdy");

PR("weight = %d,shopping = $%.2f\n",wt,sp);

第一次调用中,__VA_ARGS__展开为1个参数:

"Howdy"

第二次调用中,它展开为3个参数:

"weight = %d,shopping = $%.2f\n", wt, sp

因此展开后的代码为:

printf("Howdy");

printf("weight = %d, shopping = $.2f\n", wt ,sp);

程序清单16.5显示了一个较为复杂的示例,其中使用了字符串连接功能和#运算符:

程序清单16.5  variadic.c 程序

//variadic.c --可变宏
#include <stdio.h>
#include <math.h>
#define PR(X,...) printf("Message " #x ":" __VA_ARGS__)

int main(void)
{
    double x = 48;
    double y;

    y=sqrt(x);
    PR(1,"x = %g\n",x);
    PR(2,"x = %.2f,y = %.4f\n",x,y);

    return 0;
}

在第一个宏调用中,x的值为1,因此,#x变为“1”。展开后成为:

printf("Message " "1" ":" "x = %g\n",x);

接着连接4个字符串,把调用简化为:

printf("Message 1:x = %g\n",x);

输出如下:

Message 1: x = 48

Message 2: x =48.00,y=6.9282

记住,省略号只能代替最后的宏参数。下面的定义是错误的:

#define WRONG(x,...,y) #x #__VA_ARGS__ #y

 

 

© 著作权归作者所有

idreamo
粉丝 18
博文 139
码字总数 224743
作品 0
青岛
产品经理
私信 提问
#define 中的“ # 运算符”和“ ## 运算符”

利用宏参数创建字符串:# 运算符 在类函数宏(function-like macro)的替换部分中,“#”符号用作一个预处理运算符,它可以把语言符号(token)转化为字符串。例如,如果 x 是一个宏参量,那...

TMDJoJo
2012/07/07
0
0
C Primer Plus 第11章 11.7 ctype.h字符函数和字符串

第7章“C控制语句 分支和跳转”介绍了ctype.h系列字符相关的函数。这些函数不能被 应用于整个字符串,但是可以被应用于字符串中的个别字符。程序清单11.26定义了一个函数,它把toupper( )函数...

idreamo
2016/08/27
29
0
[编程语言]C陷阱与缺陷

内容摘要 作者以自己1985年在Bell实验室时发表的一篇论文为基础,结合自己的工作经验扩展成为这本对C程序员具有珍贵价值的经典著作。写作本书的出发点不是要批判C语言,而是要帮助C程序员绕过...

21gprs
2014/05/23
0
0
C语言基础教程之预定义和宏处理

define看起来很炫酷!C语言基础教程之预处理和宏定义 C语言预处理器 C语言预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C语言预处理器只不过是一个文本替换工具...

这个人很懒什么都没留下
2018/08/18
0
0
define看起来很炫酷!C语言基础教程之预处理和宏定义

C语言预处理器 C语言预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C语言预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处...

诸葛青云999
2018/08/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Netdata:轻量级linux服务器的性能监控工具

https://github.com/netdata/netdata/ yum install zlib-devel gcc make git autoconf autogen guile-devel automake pkgconfig -y yum install libuuid-devel zlib-devel -y wget https://g......

perofu
28分钟前
5
0
java画图工具来添加水印

用Java代码给图片加水印 不多哔哔,直接上代码: /** * @param srcImgFile 原图片文件对象 * @param outFile 输出图片文件对象 * @param waterMarkConte...

嘻嘻哈哈的忧郁
30分钟前
2
0
支付宝开源非侵入式 Android 自动化测试工具 Soloπ

前言 近年来,随着移动互联网的蓬勃发展,移动测试技术也取得了长足的进步,从早期基于测试脚本的单机自动化,到录制回放、图像识别、云测平台等测试技术贴合实际业务需求深度应用和创新,测...

shzwork
32分钟前
8
0
p3d分红逻辑学习

每次p3d余额变动,就会计算profitPerShare: profitPerShare_ += (_dividends * magnitude / (tokenSupply_)); 我的分红余额: (profitPerShare * myTokenBalanceLedger) - myPayouts 举栗子......

wmzsonic
36分钟前
8
0
CentOS7救援模式

问题 很久没有使用的一台虚拟机,忘记了root密码,这里使用救援模式,对root密码重设密码。 步骤 关机,重新开机,选中对应对版本,按“e”: 按“e” 找到“linux16”行 替代"or"为“rw in...

亚林瓜子
39分钟前
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部