预处理
第一步要进行的处理就是预处理,C语言中”#”作用是作为预处理命令,在编译器进行编译之前对源码做某些转换。
展开头文件/宏替换/去掉注释/条件编译
库文件使用
#include<***.h>
自定义头文件使用
#include"***.h"
尖括号会优先查找当前源文件的目录,然后再去搜索我们配置的搜索路径,在 CMAKE 项目中,可以使用 include_directories() 配置。
(1) 替换#define
(2)处理所有的条件编译指令,#ifdef #ifndef #endif等
(3)处理#include,将#include指向的文件插入到该行处
(4)删除所有注释
(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行
(6)保留#pragma编译器指令,因为编译器需要使用它们。
带参宏
宏会进行简单的替换,并没有类型检查
#define MAX(a ,b) a > b ? a : b
int main() {
MAX(1,MAX(1,2));
}
// 1 > 1 > 2 ? 1 : 2 ? 1 : 1 > 2 ? 1 : 2
防止出现上面的代码,我们将所有参数都加上括号
#define MAX(a ,b) (a) > (b) ? (a) : (b)
int main() {
int a = 1;
MAX(a++,2);
}
函数执行到宏替换后的位置后,a 仍然是个变量
#define MAX(a ,b) a > b ? a : b
int main() {
int a = 1;
MAX(a,2);
}
// a > 2 ? a : 2
宏传入的参数一定要是没有副作用的表达式
#define MAX(a ,b) a > b ? a : b
int main() {
int a = 1;
MAX(a++,2);
}
// a++ > 2 ? a++ : 2
宏可以进行换行
#define MAX(a ,b) \
(a) > (b) ? (a) : (b)
条件编译
#ifndef _INC_CTYPE
#define _INC_CTYPE
...
#endif
防止多次引入头文件,多次复制文件如果有重复函数原型那么编译器报错。
/*
* 1. #ifdef
* 2. #ifndef
* 3. #if
*/
三个都与 endif 配对,前两个判断宏是否 define ,if 用于判断一个值是否为真。
条件编译的用途
函数设置调试内容,也可以在 CMAKE 中设置 target_compile_definitions(${name} PUBLIC DEBUG),这样就不用手动定义 DEBUG
#define DEBUG
int Max() {
#ifdef DEBUG
#endif
}
来判断代码执行在 c++ 还是 java 中,下面代码因为 C 和 C++ 的函数编译完之后函数名的修饰不太一样需要加一些判断
#ifdef __cplusplus
extern "C" {
#endif
int add (){};
#ifdef __cplusplus
};
#endif
来看 C 的版本
__STDC_VERSION__ // 201112,19901
来看 C 的执行平台
_WINDOWS,_UNIX_
变长参数的宏
#define ADD(sum, ...) add(sum, ##__VA_ARGS__)
两个井号可以在不传参的时候去掉逗号
打印变量名
#define Printf(var) printf(#var)
预定义的宏
__FILE__
__LINE__
__FUNCTION__
FUNCTION_ 看上去是个宏,但是只有在编译的时候才知道在哪个函数当中,因此 FUNCTION_ 执行后才会替换,无法在预编译时替换
#define PRINTF(format,...) printf("("__FILE__":%d) %s : "format"\n", \
__LINE__,__FUNCTION__,##__VA_ARGS__)
打印处函数执行的文件和行数
宏代码块
如果要在宏中定义变量,最好使用代码块,但是这样会妨碍到外面的 if 语句,因此可以使用一个 do while(0) 代码块。