C语言预处理程序是不是编译器的一部分,只不过在编译过程中的一个单独的步骤。在简单来说,C语言预处理器只是一个文本替换工具,它们指示编译器实际编译之前需要做预处理。
所有的预处理命令以一个井号(#)。它必须是第一个非空字符,并且为便于阅读,一个预处理指令应该开始第一列。以下部分列出了所有重要的预处理指令:
| 指令 | 描述 |
|---|---|
| #define | 替代预处理宏 |
| #include | 从另一个文件中插入一个特殊的头 |
| #undef | 取消定义预处理宏 |
| #ifdef | 返回true,如果这个宏定义 |
| #ifndef | 返回true,如果该宏没有被定义 |
| #if | 测试是否编译时条件为true |
| #else | 用于可选#if |
| #elif | #else 一个 #if 在一条语句 |
| #endif | 结束预处理条件 |
| #error | stderr上打印错误信息 |
| #pragma | 问题特殊命令给编译器,使用一个标准化的方法 |
预处理程序示例
分析下面的实施例来理解各种指令。
#define MAX_ARRAY_LENGTH 20
这个指令实际预定义了一个宏MAX_ARRAY_LENGTH,预处理时,用20替换,使用#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 | 定义为1时,编译器符合ANSI标准 |
让我们来试试下面的例子:
#include <stdio.h>
void 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!")
字符串大小 (#)
字符串大小或数字符号运算符(’#’),当在宏定义中使用,将一个宏参数字符串常量。此运算符可使用仅在具有特定的参数或参数列表的宏。例如:
#include <stdio.h>
#define message_for(a, b)
printf(#a " and " #b ": We love you!")
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);
这个例子显示了令牌 ##n为进令牌34,在这里我们使用了两个字符串和令牌粘贴拼接。
defined() 操作符
预处理器定义的运算符采用的是常量表达式,以确定是否一个标识符使用#define定义。如果指定的标识符被定义,则该值是真(非零)。如果符号没有定义,值为false(零)。定义的运算符规定如下:
#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!
参数宏
C语言预处理器其中的一个强大的功能是模拟使用参数化的宏功能的能力。例如,我们可能有一些代码方数如下:
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
", MAX(10, 20));
return 0;
}
让我们编译和运行上面的程序,这将产生以下结果:
Max between 20 and 10 is 20