前言
预处理是程序编译的重要过程,这一次我们将学习预处理的主要内容
一、预定义符号
预定义符号主要有:
__FILE__ //表示进行编译的文件名
__LINE__ //表示文件当前的行号
__DATE__ //表示文件被编译的日期
__TIME__ //表示文件被编译的时间
__STDC__ //如果编译器遵循ANSI.C,其值为1,否则未定义
下面让我们来看一下它的运用
int main()
{
printf("FILE: %s \n Line: %d \n data: %s \ntime:%s \n ", __FILE__, __LINE__, __DATE__, __TIME__);
return 0;
}
这里最终打印出来的结果为:
二、#define
1.#define定义标识符
语法:#define name stuff
举例:
#define N 100 //在后续的代码中,便可以用,N来代替100使用,特别是在定义数组时,这样是非常方便的
#define reg register //一些经常会用到的长语句或单词就可以用它来重新定义
//如果定义的stuff过长,可以分为几行写,除了最后一行外,每行的后面都加上一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%s \t line: %d\t \
date:%s \t time: %s\n", \
__FILE__,__LINE__,
__DATE__,__TIME__)
在定义标识符时,最好不要在末尾加上 ; 以免造成语法错误
2.#define定义宏
#define机制包括了一个规定,允许把参数替换包文本中,这种实现通常称为宏(macro)
下面是宏的声明方式:
#define name(parament.list) stuff
其中的parament.list是一个有逗号隔开的符号表,他们可能出现在stuff中
需要注意的是:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释成stuff的一部分,还有在定义宏的时候,一定要加上括号,不然很容易就会出现错误。
例如:
#define MUL(x,y) x*y
printf("%d\n",MUL(5+2,3+5));
你认为这里的结果会是什么呢?
不是想象的7*8=56,而是5+2*3+5=16。宏定义中对实参表达式是不作计算字节地照样代换,这就是为什么需要加上括号的原因,而且不单单是参数要加上括号,整体也要加上括号,不然也是会出现错误。
#define ADD(x,y) (x)+(y)
printf("%d\n",10*ADD(5,3));
这里的结果也不是预想的10*(5+3)=80,而是10*5+3=53
所以对于数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
#define替换规则:
1.在调用宏时,首先会参数进行检查,看是否包含任何有#define定义的符号,如果是,他们首先被替换
2.替换文本随后被插入到程序原来文本的位置。对于宏,参数名被他们的值所替换
3.最后,在对结果文件进行扫描,看看他们是否包含任何由#define定义的符号,如果是,就重复上述处理过程
需要注意的:
宏参数和#define定中可以出现其他#define定义的符号,但是对于宏,不能出现递归
当预处理搜索#define定义的符号时,字符串常量内容并不被搜索
# 和 ##
# 可以把参数的名字插入到字符串中,
#define print(n) printf("the "#n"is %d",n);
int a = 15;
print(a);
这里打印出来的结果就是 the a is 15
## 把位于它两边的符号合并成一个符号
#define ADD(n,m) n##m
printf("%s",ADD("my"," name"));
这里进行操作之后,就会打印出 my name
宏的命名约定
宏名全部大写
三、#undef
这条指令可以移除一个宏定义
#undef name
移除name的宏定义
四、条件编译
在编译一个程序的时候我们如果将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令。
常见的条件编译指令有:
1.#if 常量表达式
//...
#endif
//常量表达式有预处理器求值
#define __DEBUG__ 1
#if __DEBUG__
//...
#endif
2.多个分钟的条件编译
#if 常量表达式
// ...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
五、文件包含
- 本地文件包含
#include "filename"
查找策略:“现在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就会提示编译错误。
- 库文件包含
查找头文件直接去标准路径下去查找,如果找不到就会提示编译错误。
所以库文件也可以用 "" 的形式包含,但是这样的查找效率就会低,而且也不容易区分到底是库文件还是本地文件了
- 嵌套文件包含
如果头文件出现被嵌套包含,就容易造成文件内容的重复。我们可以利用条件编译来解决这个问题。
在每个头文件开头写上
#ifndef __TEST_H__
#define __TEST_H__
//头文件内容
#endif //__TEST_H__
或者
#pragrma once
这样就可以避免头文件的重复引入。
总结
这次的内容就到这里,预处理是编译中重要的一环,了解它可以让你对程序有更好的看法。