⽬录
1.
预定义符号
2.
#define定义常量
3.
#define定义宏
4.
带有副作⽤的宏参数
5.
宏替换的规则
6.
宏函数的对⽐
7.
#和##
8.
命名约定
9.
#undef
10.
条件编译
11.
头⽂件的包含
一.预定义符号
看一下例子:
二.#define定义常量
格式:#define name stuff
注意:define定义的时候不要加上 ;
例如:#define MAX 1000; //它就把MAX定义为了1000;如果此时你打印MAX就会报错;
三.#define定义宏
#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(define macro)。
注意:name需要与()紧密相连,中间不能够拥有空格等其他符号;
宏定义的使用:
但是宏定义有一个值得我们
警惕的问题:
大家先看一看下面这段代码
运算方式:
由此可知:上面由替换产⽣的表达式并没有按照预想的次序进⾏求值,对此我们可以去加一个()来解决这个问题
因此我们使用宏的时候可以用到以下格式:
四.带有副作用的宏参数
当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可 能出现危险,导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果。
大家心里应该都有答案了吧,好拉接下来ctrl+f5跑起来,看看结果是啥子?
下面是该题的解释:
五.宏替换的规则
在程序中扩展#define定义符号和宏时,需要涉及⼏个步骤。
1.
在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先 被替换。
2.
替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
3.
最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。
注意:
1.
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2.
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
六.宏函数的对比
宏通常被应⽤于执⾏简单的运算。
⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些。
那为什么不用函数呢?
1.
⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏相比函数在程序的规模和速度方面更胜一筹
2.
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之 这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 >
来⽐较的类型。宏的参数是类型⽆关
的。
和函数相⽐宏的劣势:
1.
每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序 的⻓度。
2.
宏是没法调试的。
3.
宏由于类型⽆关,也就不够严谨。
4.
宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。
⽐如:宏的参数可以出现类型,但是函数做不到。
六.宏与函数的一个对比:
七.#和##
#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执⾏的操作可以理解为”字符串化“。
当我们有⼀个变量
int a = 10;
的时候,我们想打印出:
the value of a is 10
就可以写:
##
可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。
##
被称
为记号粘合 。这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
大家可以想一想,如果我们写一段代码去取出两个数的最大值的时候,不同的数据类型就得使用不同的函数。
那有没有更加简单的书写方法呢?
使用宏来定义不同的函数:
GENERIC_MAX(
int
)
//
替换到宏体内后
int##_max
⽣成了新的符号
int_max
做函数名
GENERIC_MAX(
float
)
//
替换到宏体内后
float##_max
⽣成了新的符号
float_max
做函数名
八.命名约定
⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
那我们平时的⼀个习惯是:
1.把宏名全部⼤写
2.函数名不要全部⼤写
九.#undef
该指令用于移除一个宏定义
十.条件编译
在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条 件编译指令。
⽐如说:
调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译。
十一.头文件的包含
1.本地文件下包含:
2.
Linux环境的标准头⽂件的路径:
3.
VS环境的标准头⽂件的路径:
库文件包含:
查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤
“”
的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件 了。
嵌套文件包含:
我们已经知道,
#include
指令可以使另外⼀个⽂件被编译。就像它实际出现于
#include
指令的
地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。
左边是test.c,右边是test.h
如果直接这样写,test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。
如果test.h ⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家 都能使⽤,⼜不做任何的处理,那么后果真的不堪设想。
如何解决头⽂件被重复引⼊的问题?答案:条件编译。
接下来看看例子: