一、预定义符号
前面我们学习了C语言的编译和链接。
在C语言中设置了一些预定义符号,其可以直接使用,预定义符号也是在预处理期间处理的。
如下:

可以看到上面的预定义符号,其都有两个短下划线,要注意的是,这两个短下滑一定不能少,而且这两个下划线之间是没有空的,其是挨在一起的。
下面我们来详细了解这几个预定义符号,
1、_ FILE _代表当前进行边编译的源文件,在打印时,要使用占位符%s,其不单单会打印文件名,还会将文件的完整路径打印出来。
2、_ LINE _代表出现这个预定义符号的行号,例如这个预定义的符号出现在第6行,那么其就为6,那么其打印就使用%d占位符。
3、_ DATE _代表文件被编译的日期,打印的时候使用%s占位符。
4、_ TLME _代表文件被编译的具体时间,具体到时分秒,打印的时候也是使用占位符%s。
5、_ STDC _就和编译文件的编译器有联系了,当前我们的编译器要是支持ANSI C的标准,那么其就会被定义,其值为1,反之则报错。
下面我们使用这几个预定义符号:

下面我们运行看看:
我们可以看到上面的运行结果,首先_ FILE _其将我们这个文件的完整路径打印出来了。
然后_ LINE _其就会标识当前所在的行数,然后我们打印出来的就是8。
_ DATE _代表是文件编译的时间。
_ TLME _其表示具体的时间,精确到时分秒。
那么为啥我们上面不使用第五个预定义符号呢?
这是因为我们不知道当前的编译器是否支持ANSI C,要是不支持那么程序就会报错了,所以我们下面单独使用其看看我们当前的环境是否支持:

可以看到我们写代码的时候编译器就已经报错了,下面我们运行看看其错误:

二、#define定义常量
这个关键字我们在前面的学习也已经遇到过了,下面我们来详细学习。
其定义常量的语法如下:

那么M就是我们定义的常量的名字,100是我们定义的常量的值。
我们可以使用#define来定义各种类型的常量,我们使用其定义常量,对于常量的名字有个约定,就是对名字最好使用全部大写。
这个定义常量,其本质上是替换,其在预处理后,会将程序中的M替换成100。
所以我们使用#define定义常量的时候,末尾不要加分号,因为其会将这个分号也当成常量替换进我们的程序。
如下:

可以看到此时的编译器已经报错了。
所以为了避免一些错误,我们使用#define来定义一个常量的时候,末尾不要加分号;
三、#define定义宏
#define机制中有个规定,允许将参数替换到文本中去,这种实现通常称为宏或定义宏。
如下:

其中parament-list是由一个逗号隔开的符号表,它们可能出现在stuf中。
要注意的是:参数列表的左边括号一定要和名字紧紧相邻,不要空格,不然参数就会被解释成stuf的部分了。
下面我们通过一个例子来理解:

我们发现上面宏的形式和我们的函数有点像,前面部分就相当于我们的函数名,括号里面的就相当于函数参数,不过其没有限制数据的类型,最后面的部分就相当于我们的函数体。
其实际上是替换的规则,比如SQUARE(5),那么预处理后就会将这个语句转化为5*5。
注意:
那个括号要个宏的名字紧紧挨在一起,不然可能会导致这部分被认为是后面的部分。
还有就是宏是将括号里面的x直接替换到后面的x,那么我们看看下面的写法:

我们首先是想求a+1的平方的,然后我们就传了个a+1进去替换,那么在这个语句中实际上是这样的:
a+1*a+1;
那么我们将a=5代入求得结果应该为11;那么就不是我们想要得到的结果了。

我们可以看到上面没有得到我们想要的结果是因为运算符号的优先级的问题,那么我们可以使用一个括号来解决这个问题:

这样是不是就没有问题了呢?
我们看看下面的代码:

我们大家有一开始想着的是对这个数的两倍再乘10吧,但是这个代码的结果并非所愿。
运行结果:

那么我们将其替换进去看看这个式子实际上是咋样的:
10*5+5,可以发现其也是因为运算的优先级导致的问题,此时我们是希望这个宏的结果先算出来,然后再乘10,那么我们就使用括号,改变其运算的顺序。
如下:

那么此时就得到我们想要的结果了。
四、带有副作用的宏参数
以前我们使用宏的时候可能就不知道宏参数有副作用,现在我们来好好学习一下这个知识。
带有副作用的宏参数,也就是我们在使用这个宏的时候,其可能会带来一些问题,导致我们意料之外的事情发生,不过这个副作用主要是后面的内容的部分导致的,其就是表达式求值的时候出现的永久性效果。
下面我们通过一个例子看看:

我们看上面的两个宏,在后面的内容上其表达的意思好像是一样的,但是x++,是会导致x的值发生永久改变的,然后x+1这个就对x的值没有影响。
下面我们再通过一个例子来看:

那么这个代码的运行情况是咋样的呢?我们首先就是a和b先进入这个宏,然后这个宏的语句完成后,然后a++和b++,那么此时我们认为的结果是a为6,b为3,ret为5。那么我们运行看其结果:

可以看到其结果并不是这样,那么我们具体分析一下吧,我们将进替换进去:
((a++)>(b++)?(a++):(b++));
我们将其替换进去后可以看到为啥了,首先是第一个表达式,a和b进行比大小后,进行++,然后这个结果是1,那么就执行a++,导致a进行了两次++,就使得a的值为7了,然后就进行了一次++,那么其结果就是3了,还有就是对于ret的值,其在比完大小后a此时是6,然后整个表达式的结果是第二个语句,其此时是后置++,那么就是6。
那么ret就是6了。
所以我们在使用宏的时候,最好不要使用++和--操作,不过,我们在使用宏的时候,可以先替换看看其是否可以实现我们需要达到的效果。
五、宏替换规则
我们在程序中使用宏和#define符号的时候,需要涉及到下面的几个步骤:
1、在调用宏的时候,首先会对参数进行检查,看其是否包含任何由#define定义的符号,如果有那么其会首先被替换掉。
2、替换文本随后被插入到程序中原来文本的位置,不做任何的更改,而对于宏,参数名被它们的值所替换。
3、最后,再次对结果文件进行扫描,看其是否包含任何由#define定义的符号,如果是,那么就会在进行上述的操作,直到其没有包含#define定义的符号。
我们使用宏还需要注意下面几点:
1、宏参数和#define定义可以出现其他#define定义的符号,但是对于宏,就不能出现递归的情况。
2、当预处理搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
六、宏和函数的对比
