一、 预定义符号
二、#define
#define定义标识符
宏定义后分号问题及解决方法
带副作用的宏参数
#define替换规则
#和##的使用
三、头文件包含两种方式
__FILE__ //当前进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件当前被编译的日期
__TIME__ //文件当前被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则是未定义
代码演示如下:
#include
int main()
{
printf("file:%s\n", __FILE__);
printf("line:%d\n", __LINE__);
printf("date:%s\n", __DATE__);
printf("time:%s\n", __TIME__);
return 0;
}
运行结果如下:
file:E:\code_2023\case_练习-4\case_练习-4\case.c
line:7
date:Aug 14 2023
time:13:54:28
第一种:无参的宏定义
语法形式:
#define 宏名 红内容
注:①宏名,是符号常量(不可改变),无语法检查,且无类型
②宏名通常用大写字母表示(多),但也可小写(少),遵守用户自定义标识符命名规则
③#define可以在函数外定义,也可在函数内定义,但该命令应该在单独一行上
如下例子
#define M 100
#define STR "abc"
代码演示
结果如下
当遇到个别关键字较长时,可以用宏定义简写来代替。如:
#define reg register //为register关键字创建一个简短的名字
若要定义f为寄存器变量
普通定义: register int f;
宏定义:#define reg register
reg int f;
宏替换来实现for循环
#define FOR for( ; ; )
#define FOR for(;;)
int main()
{
FOR;
return 0;
}
执行结果为死循环
在switch语句中,也可使用宏定义
在使用switch语句时,case语句后面往往会漏写break语句,此时可以采用如下方式,在写case语句时会自动把break加上
#define CASE break;case
#define CASE break;case
int main()
{
int d = 0;
switch (d)
{
case 1:
CASE 2:
CASE 3:
}
return 0;
}
替换后的代码
#define CASE break;case
int main()
{
int d = 0;
switch (d)
{
case 1:
break; case 2 :
break; case 3 :
}
return 0;
}
这里第一个break与case 1匹配,第二个break与case 2匹配,最后的case语句可以不用break。不过这样写可读性不好,但是要知道有这种写法,若遇到要能看的懂
如果说在宏定义时,宏名后面的宏的内容特别长,就会占据两行或多行,会分行写,但除了最后一行外,每行的后面都要加一个反斜杠"\"作为续行符。
#define DEBUG_PRINT printf("file:%s\t line:%d\t \
date:%s\t time:%s\n", \
__FILE__ , __LINE__ , \
__DATE__ , __TIME__)
代码如下
运行结果如下
注:续行符反斜杠"\"后不能加空格,有空格程序就会报错
在define定义标识符的时候,要不要在最后加上" ; " ? 首先来看一下代码
运行结果
但是有一种情况如果加上";"就会报错或语法错误。如下代码
上面我们说过代码在执行时会对代码先进行预编译,这时会进行宏替换,实际执行的代码是经过替换后的代码。下面是替换后的代码
可以看到替换后的代码后面会有两个分号,也就是if语句下面有两条语句,默认只跟一条语句,此时与else无法进行匹配。
因此,解决方法如下
① 面对以上情况,if....else语句后都加上花括号"{...}",其实这也是一种良好的编程习惯
②可以采用do....while()形式。具体使用看下面代码
可以看到代码没有任何错误,正常输出结果。下面来看一下预编译后替换过的代码
代码在真正执行的是上面经过与编译后的代码。
此方法具有普适性,不需要考虑那么多;第一种方法需要人工自动添加大括号,但并非每个程序人员都会自己主动去加。因此此种方法较第一种更具有普适性;当然了此种方法只需要我们知道有这种写法,或许在一些资料书籍或相关行业人员写的代码中见到,不至于看不懂或为啥这样写。
但还是建议后定义后不要加分号,省的后面麻烦。
第二种:带参的宏定义
语法形式:
#define 宏名(参数列表) 宏内容
注:①在定义有参宏时,参数列表必须用一对小括号括起且小括号和宏名之间不能有空格。如果 两者之间有任何空白存在,参数列表就会被解释为宏内容的一部分。如下:
如:宏定义求一个数的平方
注意: 在宏定义时,宏内容部分的每个参数以及整个宏内容都最好加上括号。这样可以避免出现 一些错误
具体使用如下代码说明:
这是宏内容没加任何括号,正常输出5的平方。
下面来看没有按预期输出结果的情况
这个代码的执行结果会按预期输出6的平方吗?会是36吗?下面来看执行结果
当进行宏替换时,此时第9行代码会被替换。
int c = SQUARE(a+1) ---> int c = a+1*a+1
此时a=5, 即乘法的优先级较高先算,5+5+1=11,即结果为11
这是因为没加括号,导致有优先级的问题使得没有按预想的次序运算,因此,用于对数值表达式进行求值的宏定义都应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
此时结果按预期输出36.
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
x = 10
a = x+1 //此时a增加1时,而x本身没变,即x+1 是不带副作用的
a = ++x //此时a增加1时,而x自身也变了,即x++是代副作用的
具体看下面代码演示
这段代码的执行结果会是什么呢?会是a=5、b=6、c=6。运行结果如下:
来分析一下这段代码
首先是对这段代码进行预编译,即进行宏替换,替换后的第10行代码如下
int c = ((a++)>(b++)?(a++):(b++))
图解
由于自增或自减会带有副作用,自身结果也会被修改。 因此,最终输出结果a=6、b=8、c=7.
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:①宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递 归。
②当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
首先先看一段代码
#号的使用
可以看到两种打印结果一样,可以看出字符串是有自动连接特点的。
再来看一段代码
上面的代码中,如若想要改变打印函数中字符串中不同变量时,需要写3个打印函数,看起来很繁琐。那是否能像函数那样传参呢?是可以的,这就需要用到 "#" . 具体看下面代码
替换图解
##的使用
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
如下代码演示
本地文件包含
#include "filename"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件在 标准位置查找头文件。如果找不到就提示编译错误。
库文件包含
#include
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。