1. 简介
C编译器在编译之前,会先对源程序中的代码进行预处理,称为预处理程序。预处理程序是一个单独的程序,主要处理以#开头的指令,比如宏定义、文件包含、条件编译等。
所有的预处理指令都是以#开头,通常在文件的开头使用,且声明必须在一行中,如果要换行就需要使用反斜杠\折行。
2. #define定义宏
2.1 无参宏
前面说过,#define宏可以定义常量,执行简单的文本替换操作,基本形式为:#define 宏名称 替换的值。它不是语句,所以后面不需要加分号,这样一条替换的规则,就被称为“宏”。
#define PI 3.14
#define MAX 10
#define MIN 0
上面示例中定义了三个宏,预处理程序会把源程序中的PI、MAX、MIN全都替换成对应的值3.14、10、0,宏名称遵循变量的命名规则,且不能有空格,一般以大写的形式出现;宏就是简单的文本替换,替换的值和指定的值一模一样,因此不受任何类型的限制。
通常#define指令都定义到一行,如果有多行,可以使用反斜杠“\”来换行。
#define STR "abcdefghigklmn,opq\
rstuvwxyz"
#define命令还支持多个宏包含替换,下面示例中的MAX2会被替换成100 * 100。
#define MAX 100
#define MAX2 MAX * MAX
2.2 带参宏
#define定义宏时,还可以接受一个或多个参数,与函数的参数写法类似。宏定义的参数是形式参数,宏替换的参数是实际参数,所以在进行替换的时候,不仅要宏展开,还要替换相应的参数。
//定义带参宏,注意宏名和参数括号之间不能有空格
#define MAX(A) A+A
MAX(10);//宏替换成10+10
宏的参数列表也可以是空的,但是括号还是要写,主要用于调用函数。
#define PRINTF() printf("test")
和无参宏一样,带参宏也是支持使用反斜杠\来换行处理,还可以使用更大括号{}来创建局部作用域。
#define TEST(a,b){\
printf("%d\n",a * b);
}
2.3 可变参宏
宏定义的参数的数量可以是不固定的,即可变参数的宏,就是不确定数量的参数。
#define X(a,b,...) a + b,__VA_ARGS__
//使用X宏
X(1,2,"test",10)
//宏展开后
1+2,"test",10
上面的示例中定义了一个可变参数的宏,前面有两个固定的参数a和b,后面可变参数使用…来表示,后面的替换文本中,__VA_ARGS__表示多余的参数,注意…只能放在最后。
2.4 #undef取消宏
#undef指令用来取消一个#define宏的定义。
#define MAX 100
#undef MAX //取消MAX的宏定义
3. #include
#include是文件包含指令,用于将对应的头文件(.h)引入到当前文件中,有两种形式。
第一种是使用尖括号<文件名>,表示引入的是系统提供的标准库头文件,且不需要写路径。
#include <stdio.h> //引入标准库文件
第二种是使用双引号“文件名”,表示引入的是程序员自己定义的头文件,需要指定相对目录路径。
#include "test.h" //当前目录
#include "/pro/test.h"//在其他目录
4. #if、#else、#elif、#endif
预处理程序中还可以支持条件编译,#if指令集合表示条件判断,和if…else语句类似,当#if满足条件时,就会编译内部所定义的内容,否则就不会被编译。
#if 0
int a = 5;//不满足条件
#endif
上面的示例中,#if 0表示条件不成立,所以会跳过这条语句,不会被编译。这一点和if语句很类似,后面可以跟表达式,通过判断表达式的结果来确定执行,为真就编译,为假就忽略。#endif指令用于结束一个#if指令的定义。它和#if指令是一一对应的。
#if…#endif之间还可以加入#else指令,用于处理#if指令条件不满足的情况。
#if 0
int a = 5;//不满足条件
#else
int a = 10;
#endif
#if 0不满足条件,就会执行#else指令里面的语句。
如果有多种情况需要判断时,就需要使用到#elif指令,相当于else if语句。
#define V 5
#if V == 1
printf("V等于1");
#elif V == 5
printf("V等于5");
#else
printf("V是未知的");
#endif
5. #ifdef、#endif
#ifdef…#endif指令用于判断某个宏是否定义了,通常用于判断某个头文件是否被重复引入。
#ifdef X
printf("X已经定义了\n");
#endif
如果宏X已经定义过,就会执行#ifdef指令里面的语句,否则会忽略。
#ifdef相当于#if指令的子集,它也是可以和#else指令结合使用的。
#ifdef X
printf("X已经定义了\n");
#else
printf("X没有被定义\n");
#endif
如果宏X没有被定义,则会执行#else指令中的语句。
6. #ifndef、#endif
#ifndef…#endif指令用于判断某个宏没有定义,它与#ifdef…#endif指令是对立的。
#ifndef X
printf("X没有定义\n");
#endif
上面的示例中,如果宏X没有定义,则会执行#ifndef指令里面的语句,否则会忽略。
7. 预定义宏
C语言中提供了一些预定义的宏,可以直接使用但不能修改。
- _DATE_:当前编译日期,格式为“MMM DD YYYY”的字符串;
- _TIME_:当前编译时间,格式为“HH:MM:SS”的字符串;
- _FILE_:当前文件名,也是一个字符串;
- _LINE_:当前行号,以一个十进制常量表示;
- _STDC_:定义为1,表示当前的编译器定义为标准C;
- __STDC_VERSION__:编译器当前所使用的版本,格式为“yyyymmL”的整型常量。
8. 其他
8.1 #运算符
#运算符通常用于将宏定义替换文本的参数转换为字符串形式,只能用于带参数的宏。
#define MAX(a) #a //参数前加#运算符
printf("%s\n",MAX(100));//输出字符串
上面示例中的MAX(100)展开后被替换成100,参数前加上#运算符就输出字符串,否则输出的是整数。
8.2 ##运算符
##运算符将宏的两个参数进行合并为一个标记符,通常用于生成多个变量名。
#define MAX(b) a##b
//声明多个变量
int MAX(1),MAX(2),MAX(3),MAX(4),MAX(5);//输出int a1,a2,a3,a4,a5;
8.3 #line指令
#line指令用于定义文件中的行号,可以覆盖掉预定义宏中_LINE_。
#line 100 //重置行号为100
上面的示例中将行号重定义为100,它是一个起始值,就是从下一行开始递增行号。
#line指令除了重定义行号以外,还可以改变文件名称。
#line 100 "test.h"
8.4 #error指令
#error指令用于程序在编译阶段输出错误,终止程序编译。
#define MAX 100
#ifndef MAX
#error undefined
#endif
上面的示例中,如果MAX宏未定义,则会进入#ifndef指令执行#error指令中的内容,并停止编译。
有错误的地方、需要补充的知识欢迎评论区发言、指正。