14 预处理器

预处理是在编译前对代码进行一些文本性质的操作。包括删除注释、插入#include的文件、替换#define定义的内容等。

14.1 预定义符号

预处理器对下面的符号进行了特殊的定义

#include<stdio.h>
int main ()
{
printf("__FILE__ = %s\n", __FILE__);
printf("__LINE__ = %d\n", __LINE__);
printf("__DATE__ = %s\n", __DATE__);
printf("__TIME__ = %s\n", __TIME__);
printf("__STDC__ = %d\n", __STDC__);
return 0;
}

有下面的输出

leo@leodeMBP lang_notes (master) $ gcc tmp.c
leo@leodeMBP lang_notes (master) $ ./a.out
__FILE__ = tmp.c // 源文件文件名
__LINE__ = 7 // 文件当前行行号
__DATE__ = Sep 8 2019 // 文件被编译的日期
__TIME__ = 23:41:23 // 文件被编译的时间
__STDC__ = 1 // 如果编译器遵循ANSI C,则值为1,否则值未定义

14.2 #define

define除了替换数值字面值常量外,还可以替换代码文本。如下。但不要滥用,减少代码重复性的标准做法是使用函数。

#define PROCESS_LOOP \
for (int i = 0; i < 10; i++) { \
sun += i; \
}

14.3 #define宏

宏:通过#define把参数传递到文本中。在使用宏时要特别注意两点:1替换后运算的结合性。2可能对参数的副作用。如果要移除一个宏定义,可以使用#undef name

宏与函数的区别

属性

#define宏

函数

代码长度

宏是文本替换,每次都会把代码文本替换到使用的地方

函数定义的代码文本只有一个

执行速度

没有调用和返回的开销

有额外调用和返回的开销

参数类型

宏于类型无关,只要对参数的操作合法即可

类型相关

宏的命名

由于宏和函数有诸多区别,所以明确使用的是宏还是函数就很重要了。但是c语言本身并不能在使用的时候直接区分宏和函数。为了明确区分,一般的做法是把宏名都大写。

把宏参数转换为一个字符串 #argument

#define PRINT(FORMAT, VALUE) \
print("The value of " #VALUE " is " FORMAT "\n", VALUE)
x = 22;
PRINT("%d", x+3); // 会输出 The value of x + 3 is 25

连接两边的符号 ##

最后执行的实际上是sum5 += 25,即##把sum和sum_number并成了一个变量名。

#define ADD_TO_SUM(sum_number, value) \
sum ## sum_number += value
ADD_TO_SUM(5, 25);

14.4 条件编译

常量表达式(字面值常量或#define定义的符号)为非零(真)是,statements会执行。elif只有当前面所有条件都假时,才会执行。else只有当前面所有条件都假时,才会执行。也就是说,这更像一个switch结构。

#if constant-expression
statements
#elif
other statements
#else
other statements
#endif

除了if之外,是否被被定义还有一种写法

// 下面两个等价
#if defined(symbol)
#ifdef symbol
// 下面两个等价
#if !defined(symbol)
#ifndef symbol

14.5 文件包含

大型项目中会包含很多文件,可能出现多重包含。为了避免重复包含的情况,我们可以使用下面的指令

#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/* all code. */
#endif

#define _HEADERNAME_H 1也可以写成#define _HEADERNAME_H。前者是定义成了1,而后者是定义成了空字符串。

14.6 #error和无效指令

#error用来生成错误信息,下面是用法

#if defined(OPTION_A)
stuff needed for option A
#elif defined(OPTION_B)
stuff needed for option B
#else
#error No option selected!
#endif

无效指令就是一个#开头,后面不接内容。效果和空行一样。