三、预处理指令#define详解
3.1、#define指令
#define PX printf("OK!\n");
预处理器指令 宏名 主体/替换列表
3.2、每个#define逻辑行包含三部分内容:
#define自身,缩略语(macro),主体(body)/替换列表(replacement list)
3.2.1、缩略语(macro)的命名规则:宏名中不能包含空格,遵守C变量命名规则(使用大小写字母、数字、下划线,且第一个字符不能是数字)
3.2.2、替换列表(replacement list)
预处理器在源文件中发现宏的实例后,总会用 主体(全部的主体内容、原样的主体内容) 代替宏(位于双因号中的宏不进行宏替换)
3.2.2.1、类对象宏object-like macro;宏用来代表值
常量数值
字符串(字符串中可包含其他的宏)
----------------------------------------------------------------------
使用符号常量代表数值常量的建议
用于计算式的数值常量
用于表示数组大小/循环界限的数值常量
系统的数值代码,如EOF
使用符号常量便于移植、阅读、扩展、维护
补充:const
const是C语言中的一个关键字,它限制一个 变量 的值在确定后是不能改变的
使用const可以创建:全局常量、局部常量、数字常量、数组常量、结构常量,并且宏常量可以作为const常量的初始值
----------------------------------------------------------------------
3.2.2.2、类函数宏function-like macro;带参数的宏
#define MEAN(X,Y) ((((X)*2)+(Y))/3)
预处理其指令 宏名,X/Y为宏参数 替换主体
在宏中使用参数可以创建外形和作用都类似与函数的宏,宏的参数也是用圆括号,可以有一个或多个参数,随后这些参数出现在替换主体中。类函数宏的使用类似于函数,但是二者的行为完全不同;类函数宏中的参数也相当于函数中的型参。
类函数宏的处理是发生在预处理阶段的,在该阶段预处理器只是进行文本替换而不进行数值计算,这就是二者不同行为的根本区别所在。
类函数宏体 定义时应该使用足够多的圆括号用来保证按正确的顺序进行结合和运算(每个参数都放在园括号中,整个主体放在园括号中);同时不应该向类函数宏传递++/--运算符(向函数传递++/--运算符时,实际是先计算++/--从而得到一个值,然后再把这个值传递给函数)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.2.2.2.1、# 字符串化运算符,用于类函数宏中
在类函数宏的替换主体部分中,它的作用是可以将类函数宏的参数转化为调用该宏时使用的实际参数
在类函数宏替换主体中的类函数宏参数前加上#符号,则使用 分别放在双引号中的 实际调用该类函数宏时使用的参数 替换与之对应的类函数宏的参数。
例如
#define PR(V) printf(#V"=%d",V);
...
int x = 5;
PR(x)
进行宏替换的过程是:
用实际调用类函数宏的参数 "x" 替换类函数宏的参数#V printf("x""=%d",x);
注意,此处用到了ANSI的字符串链接功能,即"str-1""str-2"被处理成"str-1str-2"
即终的替换结果是 printf("x=%d",x);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.2.2.2.2、## 组合运算符,用于类函数宏
##的作用是将两个token组合并成单个token,即将##记号前的token和##记号后的token合并成一个token
例子
#define[]AND(I,J)[]I##J;
...
AND(x,2);
进行预处理后得到的是x2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.3、重定义常量
#define[]LIMT[]25
...
#define[]LIMT[]48
这个例子叫做redefining a const,在ANSI标准中规定,第二次定义的LIMT只能与第一次定义的LIMT的完全相同(二者主体有完全相同顺序的token序列),否则报错
例如:
#define[]A[]2[][]*[][]2
#define[]A[]2[][][]*[][][]2
#define[]A[]2*2
前两个有相同的token序列(2、*、2);而第3个的token是2*2,它与前两个的都不相同
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.4、可变宏
定义方式:
#define NAME(...) printf(__VA_ARGS__);
NAME可以是任意的宏名,三个点号代表的就是可变参数
主体中的__VA_ARGS__是预定义的宏,将会使用它来处理实际调用中的可变参数(是variadic arguments的缩写)