ANSI C规定:#前可以有空格或者tab,#和指令其余部分之间也可以有空格,可以出现在任何地方,作用域从定义处到文件结尾。
因为预处理开始前,系统会删除反斜线和换行符的组合,故可以把指令扩展到几个物理行,这些物理行组成单个逻辑行。
//每个#define行(指逻辑的行):三部分组成 //指令本身 宏 替换列表(或主体) #define PI 3.141592653
宏分为类对象宏(代表值的宏)和类函数宏
宏的名字中间不能有空格,必须遵循c命名规则,从宏变成最终的替换文本叫宏展开,预处理器不进行计算,只是简单的文本替换
语言符号类型字符串和字符型字符串
系统把主体当作语言符号类型字符串,而不是字符型字符串,预处理器中的语言符号是宏定义主体里的单独的词,用空白字符把这些词分开。
#define SIX 2*2//定义里有一个语言符号,序列2*2 #define AAA 2 * 2//定义里有三个语言符号,2,*,3
若把主体解释为字符型字符串,预处理器用2 * 2替换AAA,额外的空格也算替换
若把主体解释为语言符号类型字符串,预处理器只用单个空白字符分割的三个语言符号去去替换AAA
2 * 2的空格只是分割主体语言符号的符号,不会都算(注意:不同编译器做法不一样)
重定义常量
开始把AAA定义为常量4,后来在该文件里,又把AAA定义为10,这叫重定义常量,不同编译器策略不一样,不过标准c规定:这是错误的,只允许新定义和旧定义完全相同!
//相同定义意味主体具有相同顺序的语言符号 #define A 2 * 3 #define A 2 * 3//两者等价,都是三个语言符号的主体
下面的定义被ANSI C认为不同
#define A 2*3//只有一个语言符号的主体,可以用#undef指令重新定义宏
在#define里使用参数——类函数宏
类函数宏:外形和作用和函数类似,可以使用参数,也是()括起来。一般要主体参数里都加上小括号来避免文本替换的错误发生。
#define SQRT(x) x*x int main(void) { int x = 2; printf("%d ", SQRT(2));//4 printf("%d ", SQRT(4));//16 printf("%d ", SQRT(2 + x));//8 printf("%d ", SQRT(2 + 2));//没有打印16,而是8=2 + 2 * 2 + 2 //一定主要,类函数宏也是文本替换,可以通过在主体里加圆括号来约束结合性 //避免在宏里使用增量和减量运算符 printf("%d ", SQRT(++x));//16,++x*++x,x两次增量,一次在乘法前,一次在后 printf("%d ", x);//4 //不同编译器处理不一样,4*4=16,这里x先两次自增到4,再乘。有的编译器不一样,故宏避免使用增量减量 system("pause"); return 0; }
宏里的#和##运算符
#include <stdio.h> #include <stdlib.h> #define PSQR(X) printf("x的平方 = %d ", ((X) * (X)));//注意这里的;是printf语句的分号 int main(void) { PSQR(3); system("pause"); return 0; }
结果是 x 的平方 = 9
注意,printf引号里的x被看作是普通的文本,而不是一个可以被替换的语言符号!可以在类函数宏的替换部分,使用#运算符做预处理,这样普通文本转换为可以被替换的语言符号!如果x是宏参数,那么#x可以把参数名转换为相应的字符串处理,就可以输出3,而不是X!(该过程也叫字符串化)。
#define PSQR(X) printf(#X "的平方 = %d ", ((X) * (X))); int main(void) { PSQR(3); system("pause"); return 0; }
3的平方 = 9
调用宏,用 ”3” 代替 #X ,标准C把这些字符串链接起来,产生最终结果。
#只可以用于类函数宏,而##运算符既可以用于类函数宏,也可以用到类对象宏。
##可以把两个语言符号组合为单个语言符号!
#define XNAME(n) X##n //类函数宏里,n是宏参数(一个语言符号),X是一个语言符号,加上##,两者变一个语言符号
#define PRINT_XN(n) printf("x" #n " = %d
", X ## n);//注意这里X必须大写,因为XNAME里X大写了
int main(void)
{
int XNAME(1) = 14;//相当于 int x1 = 14;
PRINT_XN(1);
system("pause");
return 0;
}
打印 x1 = 14
可变宏
函数可以接收固定参数,也可以接收可变参数(printf函数)。同样宏也是这样。
注意:可变,字符串化是c的词汇,但是固定函数,固定宏,不变宏等不是C的词汇。
实现思想:宏定义的参数列表最后一个参数为省略号…(三个点),预定义宏__VA_ARGS__就可以用在被替换部分,说明省略号的含义。
#include <stdio.h> #include <stdlib.h> #include <math.h> #define PR(X, ...) printf("可变宏:" #X __VA_ARGS__ );//注意这里是两个下划线__ int main(void) { double x = 48; double y; y = sqrt(x); //第一个参量对应宏的X,值为1,则#X 变为1,宏展开后成为: //printf("可变宏:" "1" "x = %g ", x); //然后三个字符串被编译器连接为: //printf("可变宏:1x = %g ", x); //最后指向输出 PR(1, "x = %g ", x);//__VA_ARGS__告诉了省略号代表了什么 //注意:省略号只能代表最后一个参数!!! system("pause"); return 0; }
宏是用空间换取时间,函数是用时间换取空间,比如,使用宏100次,那么会在程序里插入100次宏代表的代码,浪费空间,但是速度很快。使用函数100次,程序只有一份函数的拷贝,节省空间,但是每次调用函数,都要压栈出栈,返回调用点,浪费时间!
简单函数就能处理的功能,一般用宏,或者内联函数。
宏名字不能有空格,必须大写(约定俗成的,提醒程序员这是宏),宏不检测类型只是简单的文本替换,好处是int类型,或者double类型等都能使用。
用圆括号括起来类函数宏里的每个参数,好习惯,避免文本替换造成逻辑混乱的错误。
欢迎关注
dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!