1.预定义符号
预定义符号也是在预处理期间处理的。
写法为:
printf
(
"file:%s line:%d\n"
, __FILE__, __LINE__);
2.#define 定义常量
例子:
#define定义为整个替换,如:
加与不加';'要看具体场景
类似这种则无需加上';'
3. #define定义宏:
与上面相同,由于define为整个替换,所以下面:
就会变成
只要在宏上加上()即可解决这种问题
但是又有可能有下面这种问题
即
处理方法为在两边加上括号
4. 带有副作用的宏参数
例子:
输出结果为
原因在于,经过宏替换后,原来的z变成了
5.宏替换规则
1.
在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。
2.
替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
3.
最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1.
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2.
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
6. 宏与函数的对比
宏对比函数的优势:
1.
⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐ 函数在程序的规模和速度⽅⾯更胜⼀筹。
2.
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之 这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 >
来⽐较的类型。宏的参数是类型⽆关
的。
和函数相⽐宏的劣势:
1.
每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序 的⻓度。
2.
宏是没法调试的。
3.
宏由于类型⽆关,也就不够严谨。
4.
宏可能会带来运算符优先级的问题,导致程容易出现错。
7. #和##
1.#运算符
#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执⾏的操作可以理解为”字符串化“。
例如:
当我们有⼀个变量
int a = 10;
的时候,我们想打印出:
the value of a is 10
.
我们可以写:
# define PRINT(n) printf( "the value of " #n " is %d" , n);
# define PRINT(n) printf( "the value of " #n " is %d" , n);
PRINT(a);//当我们把a替换到宏的体内时,就出现了#a,⽽#a就是转换为"a",时⼀个字符串
代码就会被预处理为:
printf
(
"the value of ""a" " is %d"
, a);
2.##运算符
##
可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。
##
被称
为记号粘合。
这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
例如:
可以写为
type为类型,例如int,float
通过宏,我们可以很方便地创建类似的函数
8.命名约定
函数与宏的使用方法很类似,
所以我们一般:
1.将宏名全部大写。
2.函数名不要全部大写
9. #undef
用于移除一个宏定义:
10. 命令行定义
就例如在贪吃蛇中,我们创建时如果不采用宏就只能定义大小明确的数组,不方便修改。
但是采用宏,我们可以很方便的实现数组大小的更改
11. 条件编译
在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条 件编译指令。
使用方法类似if
注意:
#if后一定要加#endif,在#if和#endif之间的范围都属于执行范围
常见的条件编译指令:
12. 头⽂件的包含
1.本地文件
#
include
"filename"
查找策略:先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在 标准位置查找头⽂件。
如果找不到就提⽰编译错误。
2.库⽂件
#
include
<filename.h>
查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
注意:使用""可以用来查找库文件,但是会导致效率下降。但是不能使用<>查找本地文件。
3.嵌套⽂件包含
文件可以被重复包含。
如果test.h ⽂件⽐较⼤,这样预处理后代码量会剧增。
如何解决头⽂件被重复引⼊的问题?
答案:条件编译。
或