C 语言中的预处理

C 语言中以 # 开头的就是预处理指令,例如 #includeweb

预处理指令的用途

全部的预处理指令都会在 GCC 编译过程的预处理步骤解析执行,替换为对应的内容。在下一步编译过程当中,看不到任何预处理信息,只须要对独立的程序文件进行操做便可。svg

预处理指令的类型

  • 包含类 #include 头文件
    经过 #include 引入的头文件,会在 GCC 编译过程当中的预处理步骤进行展开,替换为完整的头文件内容。
  • 宏定义类 #define 宏名 宏体
    定义的宏在预处理时不进行语法检查,原样替换。等到编译的时候再进行语法检查。宏体若是有多个组成部分时,一般要加括号,防止发生优先级的问题。
    • 宏变量:支持表达式。例如 #define MY_VAR 3+4,使用的时候若是是这样 int i = MY_VAR * 5,会在预处理时替换为 int i = 3+4 * 5,最终获得的 i 是23。而 #define MY_VAR (3+4)则无次问题
    • 宏函数:能够将定义的宏当作函数处理。例如 #define MY_FUN(x) (3+x),使用 int i = MY_FUN(4)*5 获得的 i 是 35。
  • 条件编译 #ifdef #ifndef #else #endif
  • 预约义宏:所有以两个下划线开始和结束,方便调试
    • FILE:文件名
    • FUNCTION:函数名
    • LINE:行号,数值
    • DATE:编译的日期
    • TIME:编译的时刻

例如,对于这个 C 文件:函数

#include <stdio.h>

int main()
{
    printf("file is:%s, function is:%s, line is:%d, %s, %s", __FILE__, __FUNCTION__, __LINE__, __DATE__, __TIME__);
}

执行结果以下:ui

file is:define.c, function is:main, line is:5, Jan 19 2019, 15:10:57

宏定义

预处理时,会把全部出现宏名的地方自动替换为对应的宏值。spa

#define 宏名 宏值

宏定义的规则:debug

  • 宏值中能够有其余宏的名字,预处理时也会被替换,例如:
#define PI 3.14
#define PI2 PI * 2
  • 宏值超过一行时,能够用 \ 换行,例如:
#define PRT printf("%f ", PI); \ printf("%f", PI2)
  • 若是不须要宏值,能够定义无值的宏:
#define DEBUG
  • 预约义宏能够在任何地方直接使用。
  • 宏定义结尾不能加分号 ;,不然会被替换致使异常。

综合示例:调试

#include <stdio.h>

#define PI 3.14
#define PI2 PI*2
#define PRT printf("%f ", PI); \ printf("%f\n", PI2)
#define MIN(a, b) ((a) < (b) ? (a) : (b))

int main()
{
	PRT;
	printf("%d", MIN(2+3*5, 666));
}

有参数的宏

能够带一个或多个参数。有参数的宏的原则是:code

一切地方都有加括号,放在由于运算的优先级致使异常xml

  • 整个宏值要加括号
  • 每一个参数都有加括号
#include <stdio.h>

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define FUN1(a) (a * 5)
#define FUN2(a) (a) * 5

int main()
{
	printf("%d\n", MIN(2+3*5, 666));
	printf("%d\n", FUN1(5 + 6));		//5 + 6 * 5 = 35
	printf("%d\n", 100 / FUN2(5));	//100 / 5 * 5 = 100
}

宏定义的优缺点

  • 优势:有参数的宏定义比函数速度快,无参数的宏定义能够在C语言的各个版本中使用。
  • 缺点:没法进行参数类型的检查,可能编译经过但运行时报错。

可使用 inline 内联函数替代有参数的宏定义,速度快的同时还能够检查参数类型。token

对于 C99 以后的版本,可使用常量定义来代替无参数的宏定义:

const double PI 3.14;

条件预处理

经过条件预处理指令,能够在预处理阶段控制要编译的代码,从而实现条件预编译。对编译器来讲这个步骤是透明的,它只须要编译全部它看见的代码便可。例如:

#include <stdio.h>
#define DEBUG

int main()
{
#ifdef DEBUG
	printf("debug info");// 只有定义了 DEBUG,才会执行这段代码
#endif
	printf("hello world\n");
	return 0;
}

固然,在文件中直接定义宏的话,每次都有修改源文件,不灵活。GCC 提供了一个指定宏定义的选项 gcc -D,例如 gcc -DDEBUG -o build 1.c 会自动在代码中添加一段定义 DEBUG 的指令 #define DEBUG,这样就能够在上面的源代码中删除 #define DEBUG 这一行了。

预处理中 # 和 ## 的区别

在宏体中(宏名中不可使用)可使用一个或两个 # 做为前缀

  • #:字符串化,若是须要向宏方法中传字符串参数时可使用
  • ##:链接符号,用于链接字符串和变量

示例:

#include <stdio.h>
#define MY1(x) #x //传入的参数转为字符串
#define MY2(x) day##x //传入的参数自动拼接到 day 后面,例如出入1时为 day1

int main()
{
	int day1 = 666;
	
	printf(MY1(abc\n)); // 打印 abc 字符串后换行
	printf("%d", MY2(1));// 打印 666,即变量 day1
}
相关文章
相关标签/搜索