预处理命令是在程序编译阶段进行执行的命令,用于编译与特定环境相关的可执行文件。预处理命令扩展了 C 语言,本节将选择其中一些常用的预处理命令进行讲解。
2.8.1 宏替换命令
宏替换命令的作用类似于对源代码文件进行文本替换操作,但是其形式更为灵活丰富。编译器每次遇到宏替换命令所定义的标识符时,都会用其后的字符串替换该标识符。该命令的一般形式为:
#define 标识符 字符串
该语句结束时没有分号,所有预处理程序亦如此。在标识符和字符串之间可以有任意个空格,字符串一旦开始,仅由一新行结束。例如,如希望 TRUE 取值 1,FALSE 取值 0,可说明两个宏:
#define TRUE 1 // 使用 TRUE 表示 1 #define FALSE 0 // 使用 FALSE 表示 0
这样再程序中就能直接使用标识符来代表被宏替换的串,C 语言通过这种方法定义符号常量。另一个用法是作为宏代换。宏名可以取参量数,每次遇到宏名时,与之相连的形式参数均由程序中的实际参数代替。例如:
#define MAX(a, b) (a >= b) ? a : b // 定义宏代换 MAX(),当 a 大于或等于 b 时,返回 a 值,否则返回 b main() // 主函数 { int x = 190, y = 106; // 定义整型变量 x、y 并赋值 printf("MAX: %d", MAX(x, y)); // 输出 x 和 y 中较大的一个,使用宏代换 }
当编译程序时,MAX()定义的表达式被替换。程序第 5 行被转换为如下形式。
printf("MAX: %d", (x >=y) ? x : y);
用宏代换代替简单的函数能加快程序执行速度,因为不存在函数调用的开销,同时也提高了代码的可读性。
命令 #undef 用作取消已定义的宏名替换。一般形式为:
#undef 标识符
其主要目的是将宏替换限定在一个代码块内。如下例代码所示:
#define SUCCESS 1 // 定义宏 SUCCESS 表示 1 printf("%d", SUCCESS); // 输出宏 SUCCESS 所代表的数值 #undef SUCCESS // 取消宏 SUCCESS 的定义
在使用 #undef 命令取消宏 SUCCESS 之前,宏 SUCCESS 是有效的,所以代码第 2 行能正确地输出宏 SUCCESS 所代表的数值。
2.8.2 终止编译命令
在调试程序时,为了提高调试速度,通常在源代码的适当位置加入终止编译命令 #error。它的一般形式是:
#error 错误信息字符串
错误信息字符串不用双引号包围,当程序编译到 #error 指令时,错误信息被显示。如下例所示:
#eror MANUAL_STOP // 停止编译,并使编译器提示编译错误信息 MANUAL_STOP
当编译器编译到这条代码时,就停止工作,并将字符串 MANUAL_STOP 作为错误提示。次命令与条件编译命令配合使用,使之在特殊的条件下生效。
2.8.3 文件包含命令
文件包含命令常用于在编译时插入另一个源程序的内容。被包含文件的名字必须用一组双引号("")或一对尖括号(<>)包围,例如:
#include "filename.h" #include <stdio.h>
这两行代码都使编译器读入并编译 头文件 或 源代码 文件。双引号用于包含指定相对路径的文件,若未指明相对路径,则会在当前源文件所在的目录检索。如果文件没找到,则检索标准目录,不检索当前工作目录。尖括号用于包含标准函数库文件和用户在编译指令里所指明的函数库文件,系统会在这些函数库中搜索指定文件。
被包含的文件中也有 #include 命令是允许的,这种方式被称为嵌套的嵌入文件,嵌套层次依赖于具体实现。
2.8.4 条件编译命令
条件编译命令是编译阶段的逻辑控制结构,通常利用条件编译命令将同一源代码编译为多个不同需求的程序版本。
1. #if、#else、#elif 及 #endif
#if 命令的意义为,如果 #if 后面的常量表达式为真,则编译它 与 #endif 之间的代码,否则跳过这些代码。如下例所示:
#define X 190 // 定义整型常量 X 并赋值 #define Y 106 // 定义整型常量 Y 并赋值 #if X > Y // 条件编译命令,如果条件成立,则编译后面的语句 printf ("MAX: %d", X); // 输出提示符和X的值 #endif
上例中,因为 X 的值大于 Y,所以条件编译块中的语句被编译。如果将 #else 加入到
#if 块中,那么当 #if 后面的常量表达式为真时,则执行 #if 到 #else 间的语句,否则执行 #else
到 #endif间的语句。如下例所示:
#define X 190 // 定义整型常量 X 并赋值 #define Y 106 // 定义整型常量 Y 并赋值 #if X <= Y // 条件编译命令,如果条件成立,则编译后面的语句 printf("MIN: %d", X); // 输出提示符 和 X 的值 #else // 当前面的 #if 条件判读为假时,从这里开始编译 prinft("MIN: %d", Y); // 输出提示符和 Y 的值,此语句被编译 #endif
此代码中,因为 #if 后的表达式 “X <= Y”为结果假,所以编译器不会编译输出 X 的值的语句,而编译输出 Y 值的语句。#elif 可实现分支条件,在条件编译语句中加入 #elif 和条件表达式后,当 #if 条件为真时,编译 #if 到 #elif 间的语句。当 #if 条件为假时,判读 #elif 的条件,如果 #elif 条件为真,执行其后的代码。条件编译语句中可以有多个 #elif 语句,它们可以依次判读,但只要有一个为真,则编译完相关代码后会跳出条件编译语句。但如果 #if 和 #elif 的条件都为假,那么看代码中是否有 #else 相关的语句编译,否则不编译任何语句。如下例所示:
#define X 106 #define Y 106 #if X < Y printf("MIN: %d", X); #elif X == Y printf("X equal Y"); #else printf("MIN: %d", Y); #endif
上例中,因为 X 和 Y 的值相等,所以 #elif 分支内的代码被编译。另外条件编译命令可以在其条件编译块中嵌套另一组条件编译命令,能够嵌套多三层并没有限制。
2. #ifdef 与 #ifndef
#ifdef 命令用于判读某个宏名是否已定义,如果已定义则执行 #ifdef 与 #endif 间的代码块。#ifndef 命令用于判读某个宏名是否未定义,与前者相反。在头文件中可大量见到这组命令,它们解决了头文件循环
嵌套时反复加载同一段定义的风险。#ifdef 和 #ifndef 的命令格式为:
#ifdef 标识符
代码块
#endif
#ifndef 标识符
代码看
#endif
其中,标识符是使用 #define 所定义的宏名。#ifdef 的作用是,当其后的标识符不存在时,执行相关代码。而 #ifndef 的作用相反,当其后标识符不存在时,则执行相关代码。如下例所示:
#ifndef BASIC_ELEMENT // 判读标识符 BASIC_ELEMENT 是否被定义,未定义则编译下例语句 #define BASIC_ELEMENT // 定义标识符 BASIC_ELEMENT,该标识符没有值 #define H 1 // 条件编译块内代码 #define C 12 #define O 16 #endif // 条件编译块结束 #ifdef BASIC_ELEMENT // 如果标识符 BASIC_ELEMENT 被定义,则编译下例语句 printf("基本化学元素已定义"); // 条件编译块内代码 #endif // 条件编译块结束
这段代码首先判读是否存在 BASIC_ELEMENT 标识符。如果不存在,则将定义该标识符的语句和其他需要编译的语句一并编译。该标识符并没有任何值,它的作用纯粹是用来指示条件编译的动作,这种方式在定义头文件时很常见。代码第 7 行 使用 #ifdef 判读标识符是否存在,因前面已定已过,所以其后的相关代码会被编译。另外,#ifdef 与 #ifndef 命令也能嵌套使用。
2.8.5 修改行号命令
修改行号命令 #line 可修改编译器中所标识的源文件行号和文件名信息。编译器在编译时会为源代码的行数编号,以便编译时统计行数和指明警告或错误的行号,同时将源代码文件在文件系统中的文件名作为被编译文件的文件名信息。每行的行号由编译器预定义的宏 __LINE__ 表示,文件名信息由预定义 __FILE__ 表示,使用 #line 命令可修改这些信息。#line 命令的格式为:
#line 行号["文件名字符串"]
其中行号可以是整型常量,文件名为任意有效文件标识符。如下例所示:
#line 200[COUNT] // 将文件起始行号改为 200,文件名改为 COUNT main() // 此行成为 201 行 { // 此行成为 202 行 printf("%s : %d", __FILE__, __LINE__); // 输出文件名和行号 }
此程序输出的结果为 “COUNT:203”。使用修改行号可分割一个较大的源代码文件,或者在编译时使多个文件可以拥有连贯的信息。
2.8.6 编译指示命令
在使用 C 语言开发 Linux 程序时,编译指示命令 #pragma 非常有用。 #pragma 命令是预处理命令中最复杂的一个,其作用是设定编译器的状态,或指示编译器完成一些特定的动作。#pragma 指令对每个编译器给出了一个方法,在保持与 ANSI C 标准完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的,其格式一般为:
#pragma 参数
常见的参数如下表所示:
编译指示命令参数列表 | |
---|---|
命令 | 说明 |
message |
该参数指定编译器输出相应的信息,常用于源代码信息控制。编译信息输出窗使用方法为
当编译器遇到这条指令时,就在编译输出窗口中将消息文本打印出来 |
code_seg |
该参数可设置程序中函数代码存放的代码段,在开发驱动程序的时候常被用到。其格式为
|
once | 将此参数放在头文件的第一行可保证头文件被编译一次 |
hdrstop | 该参数表示预编译头文件到此为止,后面的头文件不进行预编译 |
resource |
将指定的文件名加入到项目中。格式为
|
warning |
该参数用于管理编译器的警告信息,如 #pragma warning( disable: 4507 )可屏蔽编号为 4507 的警告信息,
|
comment |
该指令将一个注释记录放入一个对象文件或可执行文件中,格式为
|
2.8.7 预定义的宏名
ANSI C 标准有 5 个预定义的宏名,分别是 __LINE__、__FILE__、__DATE__、__TIME__、__STDC__。
__LINE__:
代表所处的行在源代码文件中的行号;
__FILE__:
代表其所处的源代码文件的名称;
__DATE__:
代表源代码被编译成可执行文件的日期;
__TIME__:
代表源代码被编译成可执行文件的时间;
__STDC__:
用于指示编译器是否执行 ANSI C 标准,如果是,其值为 1。
2.8.8 注释
注释的作用是在源代码加便于理解其意义的信息,或者是将暂时不需要使用的代码屏蔽起来。C 语言有两种注释方法,单行注释和多行注释。单行注释用双斜杠 “//”表示注释开始,同一行中处于 “//”后的文本被当作注释。多行注释用 “/*”表示注释的开始,“*/”表示注释的结束,其间的文本被当作注释。被注释的代码会被编译器忽略,不会编译到可执行文件中。其用法如下例所示:
// 单行注释,双斜杠后的文本均被当作注释
/* 多行注释
这一行也被作为注释信息,
在本行结束注释 */