预处理器
预处理
编译一个C程序涉及很多步骤,其中第一个步骤称为预处理(preprocessing)阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括:
- 删除注释
- 插入被#include指令包含的文件的内容
- 定义和替换#define指令定义的符号
- 确认代码部分内容是否应该根据一些条件编译指令进行编译
预定义符号
__DATE__
预定义器处理时的日期__FILE__
进行编译的源文件名__LINE__
文件当前行的行号__TIME__
预定义器处理的时间__STDC_
如果编译器遵循ANSI C, 其值为1, 否则为定义
#define
指令主要有两种用法: 定义常量、宏
定义常量
#define name stuff
当源代码中出现 name
符号时都会被原封不动的替换为 stuff
。
宏
#define name(parameter1, parameter2, ...) stuff
当源代码中出现 name(parameter1, parameter2, …)
时都会被替换为 stuff
, 并且 stuff
中出现的 parameter
符号都被替换为括号内的值.
注意事项
使用 #define
指令有以下几点需要注意:
- 不要在定义或宏的最后加
;
来表示语句的结束。 - 在宏定义时为每一个宏参数以及
stuff
的最外围加上括号。 #define
内部可以嵌套#define
, 但是不能递归嵌套。- 当宏参数在宏定义中出现次数超过一次时要格外注意每次出现是宏参数的值有没有发生潜在的变化。
#define
的名字统一要大写。
宏定义的副作用
#define MAX(a,b) ((a)>(b)?(a):(b))
int x = 5;
int y = 8;
int z = MAX(x++,y++);
对于上面的式子,最终的结果:
x = 6;
y = 10;
z = 9;
因为,宏展开:
int z = ((x++)>(y++) ? (x++) : (y++)) // y比较大,自增了两次
宏与函数
属性 | #define宏 | 函数 |
---|---|---|
代码长度 | 宏是将代码嵌入到程序中。程序的代码长度会增加。 | 函数代码只出现在一个地方。每次使用这个函数时都调用那个地方的同一份代码。 |
执行速度 | 宏更快。 | 存在函数调用/返回的额外开销。 |
操作符优先级 | 宏只是简单的替换,所以代码的上下文的符号可能会改变原本的宏的优先级,除非宏有括号。 | 函数不存在这个问题。 |
参数求值 | 具有副作用的参数将会产生不可预料的结果。 | 函数不存在这个问题。 |
参数类型 | 宏与类型无关。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同参数类型的函数。 |
#undef
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。这个时候就要#undef NAME
。
条件编译
条件编译指令: #ifdef #ifndef #else #elif #endif
。
#ifdef
表示如果已经定义了#ifdef
后的符号, 则执行#ifdef
后的命令语句直到遇到#else
或#endif
, 注意:#ifdef name
等价于#if defined(name)
。#ifndef
表示如果没有定义了#ifndef
后的符号, 则执行#ifndef
后的命令语句直到遇到#else
或#endif
,注意:#ifndef name
等价于#if !defined(name)
。#else、#elif
和#endif
用于组合使用。
常见组合:
// 组合1
#ifdef name // 等价与 #if define(name)
...
#else
...
#endif
// 组合2
#ifdef name
...
#elif
...
#else
...
#endif
// 组合3
#ifndef name // 等价与 #if !define(name)
...
#else
...
#endif
// 组合4
#ifndef name
...
#endif
文件包含
#include
指令使另一个文件的内容被编译,就像它实际出现于#include
指令出现的位置一样,这种替换执行的方式很简单:预处理器删除这条指令,并用包含文件的内容取而代之。
- 函数库文件包含:
#include<filename>
。 - 本地文件包含:
#include"filename"
,如果本地没有找到,再在函数库里查找。
防止嵌套包含
#ifndef __HEADNAME__
#define __HEADNAME__ 1 // 这里1可有可无
#endif
其它指令
#error
#error
用于使预处理器发出一条错误信息, 比如:
#define PI 3.1415
#ifndef PI
#error Not define PI
#endif
当未定义PI
时, 预处理器会发出Not define PI
信息, 并停止预处理。
#line
#line
用于重置当前行号和文件名:
#line 50 // 重置当前行号为50
#line 100 "test.c" // 重置当前文件名为test.c
# 运算符
#
是字符串化”的意思,出现在宏定义中的#
是把跟在后面的参数转换成一个字符串:
#define PRINTSQUARE(a) printf("the square of a is %d\n",((a)*(a)))
PRINTSQUARE(3); // the square of a is 9
#define PRINTSQUARE(a) printf("the square of " #a " is %d\n",((a)*(a)))
PRINTSQUARE(3); // the square of 3 is 9
## 运算符
##
是一个连接符号,用于把参数连在一起:
#define XNAME(n) x##n
int XNAME(1) = 0; // 等价于: int x1 = 0;
int XNAME(2) = 1; // 等价于: int x2 = 1;
\ 连接符
\
用于连接多行预处理语句,比如:
#define MAX(a, b) ((a) > (b) ? \
(a) : (b))