C 预处理器

预计时间: 7分钟

在C源代码中插入传给编译器的指令,这些指令称为预处理器指令,预处理器指令并不是C语言的组成部分,但它们扩充了C程序设计环境。

C 预处理器

简单来说,C 预处理器只是一个文本替换工具,它指示编译器在实际编译之前进行所需的预处理。

所有预处理器指令都以井号 (#) 开头,且每条预处理器指令独占一行。

常见的C预处理器应处理指令如下

指令说明
#include文件包含
#define宏替换
#undef取消宏替换
#ifdef测试某个名字是否定义,定义了返回真
#ifndef测试某个名字是否定义,没定义返回真
#if测试条件是否为真
#else#if 的替代方案
#elif#if 的另一个测试条件
#endif#if 的结束条件
#error在标准错误输出上打印错误消息。
#pragma使用标准化方法向编译器发出特殊命令。
#line改变__LINE____FILE__的内容。

预处理器示例

分析以下示例以了解各种指令。

#define MAX_ARRAY_LENGTH 20

该指令告诉预处理器用 20 替换 MAX_ARRAY_LENGTH 的实例。使用*#define*常量来增加可读性。

#include <stdio.h>
#include "myheader.h"

这些指令告诉预处理器从系统库中获取 stdio.h并将文本添加到当前源文件。下一行告诉预处理器从本地目录获取myheader.h并将内容添加到当前源文件。

#undef  FILE_SIZE
#define FILE_SIZE 42

它告诉预处理器取消现有的 FILE_SIZE 定义并将其重新定义为 42。

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

它告诉预处理器仅当 MESSAGE 尚未定义时才定义 MESSAGE。

#ifdef DEBUG
   /* Your debugging statements here */
#endif

如果定义了 DEBUG,它会告诉预处理器处理随附的语句。

如果在编译时将-DDEBUG标志传递给 gcc 编译器,这将很有用。这将定义 DEBUG,因此您可以在编译期间即时打开和关闭调试。

预定义宏

ANSI C 定义了许多宏。

描述
__DATE__当前日期为“MMM DD YYYY”格式的字符文字。
__TIME__当前时间为“HH:MM:SS”格式的字符文字。
__FILE__这包含作为字符串文字的当前文件名。
__LINE__这包含作为十进制常量的当前行号。
__STDC__当编译器符合 ANSI 标准时定义为 1。

让我们试试下面的例子 -

#include <stdio.h>

int main() {

   printf("File :%s\n", __FILE__ );
   printf("Date :%s\n", __DATE__ );
   printf("Time :%s\n", __TIME__ );
   printf("Line :%d\n", __LINE__ );
   printf("ANSI :%d\n", __STDC__ );

}

当文件test.c中的上述代码被编译并执行时,它会产生以下结果 -

File :test.c
Date :Jun 2 2012
Time :03:36:24
Line :8
ANSI :1

预处理器运算符

C 预处理器提供以下运算符来帮助创建宏 -

宏继续 (\) 运算符

宏通常仅限于一行。宏继续运算符 (\) 用于继续一个对于单行来说太长的宏。例如 -

#define  message_for(a, b)  \
   printf(#a " and " #b ": We love you!\n")

字符串化 (#) 运算符

字符串化或数字符号运算符 ( '#' ) 在宏定义中使用时,会将宏参数转换为字符串常量。此运算符只能用于具有指定参数或参数列表的宏中。例如 -

#include <stdio.h>

#define  message_for(a, b)  \
   printf(#a " and " #b ": We love you!\n")

int main(void) {
   message_for(Carole, Debra);
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Carole and Debra: We love you!

标记粘贴 (##) 运算符

宏定义中的标记粘贴运算符 (##) 结合了两个参数。它允许将宏定义中的两个单独的标记连接成一个标记。例如 -

#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void) {
   int token34 = 40;
   tokenpaster(34);
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

token34 = 40

之所以发生这种情况,是因为此示例导致预处理器的以下实际输出 -

printf ("token34 = %d", token34);

这个例子展示了 token##n 到 token34 的连接,这里我们同时使用了字符串化和标记粘贴。

Defined() 运算符

defined运算符用于常量表达式,以确定是否使用#define 定义了标识符。如果定义了指定的标识符,则值为真(非零)。如果未定义符号,则值为假(零)。定义的运算符指定如下 -

#include <stdio.h>

#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void) {
   printf("Here is the message: %s\n", MESSAGE);  
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Here is the message: You wish!

参数化宏

预处理器的强大功能之一是能够使用参数化宏来模拟功能。例如,我们可能有一些代码来平方一个数字,如下所示 -

int square(int x) {
   return x * x;
}

我们可以使用宏重写上面的代码,如下所示 -

#define square(x) ((x) * (x))

带参数的宏必须先使用**#define**指令定义,然后才能使用。参数列表括在括号中,并且必须紧跟在宏名称之后。宏名称和左括号之间不允许有空格。例如 -

#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void) {
   printf("Max between 20 and 10 is %d\n", MAX(10, 20));  
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Max between 20 and 10 is 20
更新于2022年04月13日