预定义符号:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
ps:这里只包含了部分预定义符号
这些预定义符号都是可以直接使用的,for example:
printf("%s\n",__FILE__); //这里直接打印文件名
printf("%d\n",__LINE__);//这里直接打印这句printf语句的行号
#define
定义标识符
怎么使用:
#define MAX 10000 //定义常量MAX值为10000
#define reg register //为register定义一个更简短的名字
#define for_loop for( ; ;) //用自己的语言来替换for循环实现
#define CASE break;case //在写case语句的时候自动把break写上
如果定义的语句过长,可以分行写。除了最后一行外,每行的后面都加一个反斜杠('\')
#define CONFIG_PRINT printf("%s %s", \
__FILE__,__DATE__)
是否要在define语句最后加;
#define机制包括了一个规定,允许把参数
替换到文本中,这种实现通常称为宏(marco)或定义宏(define macro).
宏的声明方式:
#define name(parament-list) stuff
其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。
warning:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
如:
#define SQUARE( x ) x*x
假如程序中有一句语句
printf("%d\n",SQUARE(3));
程序在进行预处理时会把该语句中的SQUARE(3)替换成3*3变为:
printf("%d\n",3*3);
但是这个宏存在危险操作:
int a = 5;
printf("%d\n", SQUARE(a+1));
预编译之后的代码是printf("%d\n",a+1*a+1);
解决的方法是 #define SQUARE(x) (x)*(x)
这样预处理之后的代码是:printf("%d\n",(a+1)*(a+1));
再看一个代码:
#define DOUBLE(x) (x)+(x)
printf("%d\n",10 * DOUBLE(a));
预处理之后的代码是printf("%d\n",10*a+a);
我们预期的结果是先求出DOUBLE(a)的值,再乘10.但是预编译的运算顺序是10先乘a,再加a。
解决的方法是#define DOUBLE(x) ((x)+(x))
#define 替换规则
在程序中拓展#define定义符号和宏时,需要涉及几个步骤
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
point:
宏参数和#define定义中可以出现其他#define定义符号。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号的时候,字符串常量的内容不被搜索。
#和##
函数可以传参修改打印的值,但是没法修改打印的字符串,上面的the value of a/b is %d\n我们就没法修改。但是宏可以:
将要打印的值改为"#n"放入字符串中
##
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
这里的##将传参s1(f)和s2(un)结合,打印的%d就是fun的值。
带副作用的宏参数
当传参包含++或--时,一定要谨慎使用。
比如
把宏调用拓展开:
先是a++和b++比较,然后各加了1;b>a,返回b++,b又加了1,所以打印的结果是 4 6 5.
宏和函数的对比
宏通常被应用于执行简单的运算。
好处在于用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更优。第二点函数的参数必须声明类型。所以函数只能在类型合适的表达式上使用。反之宏能适用于多种类型。宏是类型无关的。
缺点
每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
宏是无法调试的。
宏由于类型无关,也就不够严谨。
宏可能会带来运算符优先级的问题,导致程序出错。