当前位置:嗨网首页>书籍在线阅读

03-明示常量_#define

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

16.2 明示常量: #define

#define 预处理器指令和其他预处理器指令一样,以 # 号作为一行的开始。ANSI和后来的标准都允许 # 号前面有空格或制表符,而且还允许在 # 和指令的其余部分之间有空格。但是旧版本的C要求指令从一行最左边开始,而且 # 和指令其余部分之间不能有空格。指令可以出现在源文件的任何地方,其定义从指令出现的地方到该文件末尾有效。我们大量使用 #define 指令来定义明示常量(manifest constant)(也叫作符号常量),但是该指令还有许多其他用途。程序清单16.1演示了 #define 指令的一些用法和属性。

程序清单16.1  preproc.c 程序

/* preproc.c -- 简单的预处理示例 */
#include <stdio.h>
#define TWO 2    /* 可以使用注释 */
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde" /* 反斜杠把该定义延续到下一行 */
#define FOUR TWO*TWO
#define PX printf("X is %d.\n", x)
#define FMT "X is %d.\n"
int main(void)
{
   int x = TWO;
   PX;
   x = FOUR;
   printf(FMT, x);
   printf("%s\n", OW);
   printf("TWO: OW\n");
   return 0;
}

预处理器指令从 # 开始运行,到后面的第1个换行符为止。也就是说,指令的长度仅限于一行。然而,前面提到过,在预处理开始前,编译器会把多行物理行处理为一行逻辑行。

每行 #define (逻辑行)都由3部分组成。第1部分是 #define 指令本身。第2部分是选定的缩写,也称为宏。有些宏代表值(如本例),这些宏被称为类对象宏(object-like macro)。C语言还有类函数宏(function-like macro),稍后讨论。宏的名称中不允许有空格,而且必须遵循C变量的命名规则:只能使用字符、数字和下划线( _ )字符,而且首字符不能是数字。第3部分(指令行的其余部分)称为替换列表或替换体(见图16.1)。一旦预处理器在程序中找到宏的示实例后,就会用替换体代替该宏(也有例外,稍后解释)。从宏变成最终替换文本的过程称为宏展开(macro expansion)。注意,可以在 #define 行使用标准C注释。如前所述,每条注释都会被一个空格代替。

83.png

图16.1 类对象宏定义的组成

运行该程序示例后,输出如下 :

X is 2.
X is 4.
Consistency is the last refuge of the unimaginative. - Oscar Wilde
TWO: OW

下面分析具体的过程。下面的语句:

int x = TWO;

变成了:

int x = 2;

2 代替了 TWO 。而语句:

PX;

变成了:

printf("X is %d.\n", x);

这里同样进行了替换。这是一个新用法,到目前为止我们只是用宏来表示明示常量。从该例中可以看出,宏可以表示任何字符串,甚至可以表示整个C表达式。但是要注意,虽然 PX 是一个字符串常量,它只打印一个名为 x 的变量。

下一行也是一个新用法。读者可能认为 FOUR 被替换成 4 ,但是实际的过程是:

x = FOUR;

变成了:

x = TWO*TWO;

即是:

x = 2*2;

宏展开到此处为止。由于编译器在编译期对所有的常量表达式(只包含常量的表达式)求值,所以预处理器不会进行实际的乘法运算,这一过程在编译时进行。预处理器不做计算,不对表达式求值,它只进行替换。

注意,宏定义还可以包含其他宏(一些编译器不支持这种嵌套功能)。

程序中的下一行:

printf (FMT, x);

变成了:

printf("X is %d.\n",x);

相应的字符串替换了 FMT 。如果要多次使用某个冗长的字符串,这种方法比较方便。另外,也可以用下面的方法:

const char * fmt = "X is %d.\n";

然后可以把 fmt 作为 printf() 的格式字符串。

下一行中,用相应的字符串替换 OW 。双引号使替换的字符串成为字符串常量。编译器把该字符串存储在以空字符结尾的数组中。因此,下面的指令定义了一个字符常量:

#define HAL 'Z'

而下面的指令则定义了一个字符串( Z\0 ):

#define HAP "Z"

在程序示例16.1中,我们在一行的结尾加一个反斜杠字符使该行扩展至下一行:

#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde"

注意,第2行要与第1行左对齐。如果这样做:

#define OW "Consistency is the last refuge of the unimagina\
   tive. - Oscar Wilde"

那么输出的内容是:

Consistency is the last refuge of the unimagina tive. - Oscar Wilde

第2行开始到 tive 之间的空格也算是字符串的一部分。

一般而言,预处理器发现程序中的宏后,会用宏等价的替换文本进行替换。如果替换的字符串中还包含宏,则继续替换这些宏。唯一例外的是双引号中的宏。因此,下面的语句:

printf("TWO: OW");

打印的是 TWO: OW ,而不是打印:

2: Consistency is the last refuge of the unimaginative. - Oscar Wilde

要打印这行,应该这样写:

printf("%d: %s\n", TWO, OW);

这行代码中,宏不在双引号内。

那么,何时使用字符常量?对于绝大部分数字常量,应该使用字符常量。如果在算式中用字符常量代替数字,常量名能更清楚地表达该数字的含义。如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数。如果数字是系统代码(如, EOF ),用符号常量表示的代码更容易移植(只需改变 EOF 的定义)。助记、易更改、可移植,这些都是符号常量很有价值的特性。

C语言现在也支持 const 关键字,提供了更灵活的方法。用 const 可以创建在程序运行过程中不能改变的变量,可具有文件作用域或块作用域。另一方面,宏常量可用于指定标准数组的大小和 const 变量的初始值。

#define LIMIT 20
const int LIM = 50;
static int data1[LIMIT];   // 有效
static int data2[LIM];    // 无效
const int LIM2 = 2 * LIMIT  // 有效
const int LIM3 = 2 * LIM;  // 无效

这里解释一下上面代码中的“无效”注释。在C中,非自动数组的大小应该是整型常量表达式,这意味着表示数组大小的必须是整型常量的组合(如 5 )、枚举常量和 sizeof 表达式,不包括 const 声明的值(这也是C++和C的区别之一,在C++中可以把 const 值作为常量表达式的一部分)。但是,有的实现可能接受其他形式的常量表达式。例如,GCC 4.7.3不允许 data2 的声明,但是Clang 4.6允许。