预处理详解
预处理符号
__FILE__ // 进行编译的源文件
__LINE__ // 文件当前的行号
__DATE__ // 文件被编译的日期
__TIME__ // 文件被编译的时间
__SIDC__ // 如果编译器遵循ANSI C,其值为1,否则未定义
这写预定义符号都是语言内置的。
printf("file:%s line:%d\n", __FILE__, __LINE__);
#define
- #define 定义标识符
语法: #define name stuff
举例:
#define MAX 1000
#define reg register // 为 register 这个关键字创建一个简短的名字为 reg
#define do_forever for(; ;) // 用更形象的符号来替换一种实现
// 如果定义的 stuff 过长,可以分成几行写,除最后一行外,每行的后面都加上一个反斜杠('\')这里也叫续行符。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n", \
__FILE__, __LINE__, \
__DATE__, __TIME__ )
此处拓展反斜杠 ‘ \ ’(表示转义):
- 将普通字符转换成特殊含义:\n : 表程序换行
- 将特殊含义转化成字面操作:\ : 输出 \
- 作折行操作。
注意:
- 宏处理只是进行简单的文本替换,而不做任何检查。
- 定义宏时,后面一定不要加 ;
- #define 定义宏函数
语法:
#define name(parament-list) stuff
// parament-list 是一个由逗号隔开的符号表,它们可以出现在 stuff 中
// 注意:
// 1. 参数列表的左括号必须与name紧邻;
// 2. 如果两者之间有任何空白存在,参数列表就会被解析为 stuff 的一部分。
举例:
#define SQUARE(x) x*x
int main()
{
printf("%d\n",SQUARE(5));
}
// 输出25
注意:定义宏时,一定要注意括号
例1:
#define SQUARE(x) x*x
int main()
{
int a = 5;
printf("%d\n", SQUARE(a + 1));
system("pause");
return 0;
}
// 替换文本时,x 被替换成 a+1,所以就变成了:
printf("%d\n", a+1*a+1); 输出结果为11
// 而和我们期待的结果36不一致,要想解决此问题,很简单,只需在宏定义时加上括号。
#define SQUARE(x) (x)*(x)
// 这样预处理之后,就会产生预期的效果:
printf("%d\n", (a + 1)*(a + 1));
// 输出结果为36
由例1可知:用于数值表达式进行求值的宏定义,都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
- #define 替换
在程序中扩展 #define 定义符号和宏时,需要涉及以下步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何有 #define 定义的符号。如果有它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后欧,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,重复上述过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的变量,但是,对于宏,不能出现递归(即自己不能宏定义自己)。
2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。
- # 和 ##
字符串是有自动连接的特点。
char *p = "hello""world\n";
printf("%s", p); // 输出 helloworld
- 只有当字符串作为宏参数时,才可以把字符串放在字符串中。
#define PRINT(FORMAT,VALUE) \
printf("the value is "FORMAT"\n", VALUE);
……
PRINT("%d",10);
// 结果:the value is 10
- 使用 #,把一个宏参数变成对应的字符串
int i=10;
#define PRINT(FORMAT,VALUE) \
printf("the value of" #VALUE "is" "FORMAT" \n",VALUE);
……
PRINT("%d", i+3);
// 结果:the value of i+3 is 13
- ## 的作用
## 可以把位于它两边的符号合成一个新的符号。
它允许宏定义从分离的文本片段创建标识符。
#define ADD_TO_SUM(num,value)
sum##num+=value;
……
ADD_TO_SUM(5,10);
// 作用是:给 sum5 增加10
注意:这样的连接必须产生一个合法的标识符(即已经定义的),否则其结果就是未定义的。
宏和函数
宏通常被应用于执行简单的运算。
举例:在两个数中找出较大的一个。
#define MAX(a,b) ((a)>(b)?(a):(b))
宏相对于函数的优点:
- 宏比函数在程序的规模和速度方面更胜一筹。
- 宏是与类型无关的。
宏相比于函数的劣势:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于与类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致容易出错。
宏有事可以做到函数做不到的事情。
宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num,type) \
(type *)malloc(num * sizeof(type))
……
// 使用
MALLOC(10,int); // 类型作为参数
// 预处理之后
(int *)malloc(10 * sizeof(int));
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候 ,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预料的后果。副作用就是表达式求值的时候出现的永久性效果。
举例:
y=x+1;
// 不带副作用,因为将 x+1 赋给 y 后,x 的值没有发生改变
y=x++;
// 带副作用,因为此为后置++,先将 x 的值赋给 y ,再给 x+1,所以 x 的值发生了变化。
命名约定
宏名全部大写;函数名不要全部大写。
宏和函数的一个对比:
属性 | define 宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都要被插入到程序中。除了非常小的宏之外,程序的长度将大幅度增加 | 函数代码只出现于同一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用、返回的额外开销 |
操作符优先级 | 宏参数求值是在所有周围环境表达式的上下文环境里,除非他们加上括号,否则邻近操作符的优先级可能会产生不可预期的结果 | 参数在函数被调用前只求值一次,它将结果值传递给函数。表达式的求值结果更容易预测 |
参数求值 | 参数每次用于宏定义时,他们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果 | 参数在函数被调用前只求值一次,在函数中多次使用参数并不会导致多种求值的过程。参数的副作用并不会造成任何特殊的问题 |
参数类型 | 宏和类型无关。只要对参数的操作是合法的,他可以适用于任何类型 | 函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的 |
#undef
用于移除一个宏定义。
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
#undef NAME