在C语言中以#开头的语句都叫做预处理指令,因为这些不是标准的C代码,不能被编译器直接编译,需要一段程序把它翻译成标准的C代码,负责翻译的程序叫预处理器,翻译的过程叫预处理。
gcc -E code.c
把预处理的结果显示在终端上
gcc -E code.c -o code.i
把预处理的结果存储到文件中(-o :重命名,为执行/编译的结果重新取名)
例:gcc hello.c -o hello -> 原本 hello.c -o 出来 a.out,现在叫 hello
文件包含
#include < >
从系统指定的路径加载头文件
#include " "
先从当前路径加载头文件,如果没有再从系统指定的路径加载头文件
系统指定路径:
1、操作系统通过设置环境变量来指定加载头文件的路径
2、-I 路径:编译时指定加载头文件的路径
gcc test.c -I. // . :表示当前目录
gcc test.c -I/home/text/ // 写出具体路径也可以
头文件卫士
防止头文件被重复包含
#ifndef FILE_H
注意:在标准库里面:# ifndef _ STDIO _ H【文件名前有小横杠( _ )】,自己写的不加( _ ),要以示区别
ifndef 用来判断是否有定义此文件名,没有则为真
# define FILE_H
…
#endif // FILE_H
头文件卫士作用简析:
若有 a.h、b.h、c.h
其中 b.h 包含 a.h
且 c.h 包含 a.h 再包含 b.h
这样a.h 在 c.h 里面变成有两份,而头文件卫士可以解决这种问题
注意:
头文件卫士不能解决循环包含的问题
a.h 包含 b.h, 而b.h 包含 a.h
则 a.h,b.h 里参数和函数都需要互相调用,导致编译器不知道要先调用a还是先调用b
因此会出现隐式声明
解决办法:
把a.h 和 b.h 共用的内存提出来再写一个 c.h ,然后 a.h 和 b.h 都包含 c.h
宏定义
单纯宏 #define NAME
可以配合头文件卫士,条件编译使用
宏常量 #define PI 3.14
预处理器会把代码中的所有宏名替换成它后面的数字
好处:
1、比用变量安全性高
2、比直接使用数据的扩展性强
3、可以为数据取一个有意义的名字,提高可读性
注意:定义宏时不能带分号,一般宏名都全部大写
宏函数
带参数的宏,并不是真正的函数,只是使用样式与函数一样
其实就是把一个复杂的公式抽象成一个像函数一样的宏
如果宏函数的内容太长,可以使用大括号,但是不能换行(可以使用续行符 \ )
#define SUM(a,b) a+b
#define swap(x,y) {\
if(sizeof(x) == sizeof(int))\
{\
int* p1 = (int*)&x;\
int* p2 = (int*)&y;\
*p1 ^= *p2;\
*p2 ^= *p1;\
*p1 ^= *p2;\
}\
else\
{\
long long* p1 = (long long*)&x;\
long long* p2 = (long long*)&y;\
*p1 ^= *p2;\
*p2 ^= *p1;\
*p1 ^= *p2;\
}\
}
优点:
1、方便使用
2、速度快(因为没有进行参数传递、压栈、出栈、释放栈的过程)
缺点:
1、不会对参数进行类型检查(只是简单的替换),安全性差
2、没有返回值,只有一个运算结果,因此处理不了很复杂的业务逻辑
3、过多使用会增加代码段的冗余(每次调用,代码段就会增加一段,不像函数只有一段)
注意:
1、二义性(环境不同运算规则不同)
-> 给每一个参数都加上( )可以尽可能地避免二义性
2、调用宏函数时不要使用自变运算符(自变运算符会在宏里面加减多次)