预处理

第一步要进行的处理就是预处理,C语言中”#”作用是作为预处理命令,在编译器进行编译之前对源码做某些转换。

展开头文件/宏替换/去掉注释/条件编译

  1. 库文件使用
  2. #include<***.h>
  3. 自定义头文件使用
  4. #include"***.h"

尖括号会优先查找当前源文件的目录,然后再去搜索我们配置的搜索路径,在 CMAKE 项目中,可以使用 include_directories() 配置。

(1) 替换#define
(2)处理所有的条件编译指令,#ifdef #ifndef #endif等
(3)处理#include,将#include指向的文件插入到该行处
(4)删除所有注释
(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行
(6)保留#pragma编译器指令,因为编译器需要使用它们。

带参宏

宏会进行简单的替换,并没有类型检查

  1. #define MAX(a ,b) a > b ? a : b
  2. int main() {
  3. MAX(1,MAX(1,2));
  4. }
  5. // 1 > 1 > 2 ? 1 : 2 ? 1 : 1 > 2 ? 1 : 2

防止出现上面的代码,我们将所有参数都加上括号

  1. #define MAX(a ,b) (a) > (b) ? (a) : (b)
  2. int main() {
  3. int a = 1;
  4. MAX(a++,2);
  5. }

函数执行到宏替换后的位置后,a 仍然是个变量

  1. #define MAX(a ,b) a > b ? a : b
  2. int main() {
  3. int a = 1;
  4. MAX(a,2);
  5. }
  6. // a > 2 ? a : 2

宏传入的参数一定要是没有副作用的表达式

  1. #define MAX(a ,b) a > b ? a : b
  2. int main() {
  3. int a = 1;
  4. MAX(a++,2);
  5. }
  6. // a++ > 2 ? a++ : 2

宏可以进行换行

  1. #define MAX(a ,b) \
  2. (a) > (b) ? (a) : (b)

条件编译

  1. #ifndef _INC_CTYPE
  2. #define _INC_CTYPE
  3. ...
  4. #endif

防止多次引入头文件,多次复制文件如果有重复函数原型那么编译器报错。

  1. /*
  2. * 1. #ifdef
  3. * 2. #ifndef
  4. * 3. #if
  5. */

三个都与 endif 配对,前两个判断宏是否 define ,if 用于判断一个值是否为真。

条件编译的用途

函数设置调试内容,也可以在 CMAKE 中设置 target_compile_definitions(${name} PUBLIC DEBUG),这样就不用手动定义 DEBUG

  1. #define DEBUG
  2. int Max() {
  3. #ifdef DEBUG
  4. #endif
  5. }

来判断代码执行在 c++ 还是 java 中,下面代码因为 C 和 C++ 的函数编译完之后函数名的修饰不太一样需要加一些判断

  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
  4. int add (){};
  5. #ifdef __cplusplus
  6. };
  7. #endif

来看 C 的版本

  1. __STDC_VERSION__ // 201112,19901

来看 C 的执行平台

  1. _WINDOWS,_UNIX_

变长参数的宏

  1. #define ADD(sum, ...) add(sum, ##__VA_ARGS__)

两个井号可以在不传参的时候去掉逗号

打印变量名

  1. #define Printf(var) printf(#var)

预定义的宏

  1. __FILE__
  2. __LINE__
  3. __FUNCTION__

FUNCTION_ 看上去是个宏,但是只有在编译的时候才知道在哪个函数当中,因此 FUNCTION_ 执行后才会替换,无法在预编译时替换

  1. #define PRINTF(format,...) printf("("__FILE__":%d) %s : "format"\n", \
  2. __LINE__,__FUNCTION__,##__VA_ARGS__)

打印处函数执行的文件和行数

宏代码块

如果要在宏中定义变量,最好使用代码块,但是这样会妨碍到外面的 if 语句,因此可以使用一个 do while(0) 代码块。