C 语言中的常量和预处理器

在程序的指导下,计算机可以做许多事情,如数值计算、名字排序、执行语言或视频命令、计算彗星轨道、准备邮件列表、拨电话号码、画画、做决策或其他你能想到的事情。要完成这些任务,程序需要使用数据,即承载信息的数字和字符。有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为常量(constant)。其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)。在示例程序中,weight 是一个变量,14.5833 是一个常量。那么,1700.0 是常量还是变量?在现实生活中,白金的价格不会是常量,但是在程序中,像 1700.0 这样的价格被视为常量。

有时,在程序中要使用常量。例如,可以这样计算圆的周长:

1
circumference = 3.14159 * diameter;

这里,常量 3.14159 代表著名的常量 pi(π)。在该例中,输入实际值便可使用这个常量。然而,这种情况使用符号常量(symbolic constant)会更好。也就是说,使用下面的语句,计算机稍后会用实际值完成替换:

1
circumference = pi * diameter;

为什么使用符号常量更好?首先,常量名比数字表达的信息更多。请比较以下两条语句:

1
2
owed = 0.015 * housevalue;
owed = taxrate * housevalue;

如果阅读一个很长的程序,第2条语句所表达的含义更清楚。

另外,假设程序中的多处使用一个常量,有时需要改变它的值。毕竟,税率通常是浮动的。如果程序使用符号常量,则只需更改符号常量的定义,不用在程序中查找使用常量的地方,然后逐一修改。

那么,如何创建符号常量?方法之一是声明一个变量,然后将该变量设置为所需的常量。可以这样写:

1
2
float taxrate;
taxrate = 0.015;

这样做提供了一个符号名,但是 taxrate 是一个变量,程序可能会无意间改变它的值。C 语言还提供了一个更好的方案——C 预处理器。预处理器也可用来定义常量。只需在程序顶部添加下面一行:

1
#define TAXRATE 0.015

编译程序时,程序中所有的 TAXRATE 都会被替换成 0.015。这一过程被称为编译时替换(compile-time substitution)。在运行程序时,程序中所有的替换均已完成(见图 5)。通常,这样定义的常量也称为明示常量(manifest constant)。

请注意格式,首先是 #define,接着是符号常量名(TAXRATE),然后是符号常量的值(0.015)(注意,其中并没有 = 符号)。所以,其通用格式如下:

1
#define NAME value

实际应用时,用选定的符号常量名和合适的值来替换 NAME 和 value。注意,末尾不用加分号,因为这是一种由预处理器处理的替换机制。为什么 TAXRATE 要用大写?用大写表示符号常量是 C 语言一贯的传统。这样,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。大写常量只是为了提高程序的可读性,即使全用小写来表示符号常量,程序也能照常运行。尽管如此,初学者还是应该养成大写常量的好习惯。

另外,还有一个不常用的命名约定,即在名称前带 c 或 k 前缀来表示常量(如,c_level 或 k_line)。

符号常量的命名规则与变量相同。可以使用大小写字母、数字和下划线字符,首字符不能为数字。程序清单 4 演示了一个简单的示例。

输入的内容和编译后的内容

图 5 输入的内容和编译后的内容

程序清单 4 pizza.c 程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* pizza.c -- 在比萨饼程序中使用已定义的常量 */
#include <stdio.h>
#define PI 3.14159
int main(void)
{
     float area, circum, radius;

     printf("What is the radius of your pizza?\n");
     scanf("%f", &radius);
     area = PI * radius * radius;
     circum = 2.0 * PI *radius;
     printf("Your basic pizza parameters are as follows:\n");
     printf("circumference = %1.2f, area = %1.2f\n", circum, area);

     return 0;
}

printf() 语句中的 %1.2f 表明,结果被四舍五入为两位小数输出。下面是一个输出示例:

1
2
3
4
What is the radius of your pizza?
6.0
Your basic pizza parameters are as follows:
circumference = 37.70, area = 113.10

#define 指令还可定义字符和字符串常量。前者使用单引号,后者使用双引号。如下所示:

1
2
3
4
#define BEEP '\a'
#define TEE 'T'
#define ESC '\033'
#define OOPS "Now you have done it!"

记住,符号常量名后面的内容被用来替换符号常量。不要犯这样的常见错误:

1
2
/* 错误的格式 */
#define TOES = 20

如果这样做,替换 TOES 的是 = 20,而不是 20。这种情况下,下面的语句:

1
digits = fingers + TOES;

将被转换成错误的语句:

1
digits = fingers + = 20;

一、const 限定符

C90 标准新增了 const 关键字,用于限定一个变量为只读1。其声明如下:

1
const int MONTHS = 12; // MONTHS在程序中不可更改,值为12

这使得 MONTHS 成为一个只读值。也就是说,可以在计算中使用 MONTHS,可以打印 MONTHS,但是不能更改 MONTHS 的值。const 用起来比 #define 更灵活。

二、明示常量

C 头文件 limits.h 和 float.h 分别提供了与整数类型和浮点类型大小限制相关的详细信息。每个头文件都定义了一系列供实现使用的明示常量。例如,limits.h 头文件包含以下类似的代码:

1
2
#define INT_MAX +32767
#define INT_MIN -32768

这些明示常量代表 int 类型可表示的最大值和最小值。如果系统使用 32 位的 int,该头文件会为这些明示常量提供不同的值。如果在程序中包含 limits.h 头文件,就可编写下面的代码:

1
printf("Maximum int value on this system = %d\n", INT_MAX);

如果系统使用 4 字节的 int,limits.h 头文件会提供符合 4 字节 int 的 INT_MAX 和 INT_MIN。表 1 列出了 limits.h 中能找到的一些明示常量。

表 1 limits.h 中的一些明示常量

明示常量 含义
CHAR_BIT char 类型的位数
CHAR_MAX char 类型的最大值
CHAR_MIN char 类型的最小值
SCHAR_MAX signed char 类型的最大值
SCHAR_MIN signed char 类型的最小值
UCHAR_MAX unsigned char 类型的最大值
SHRT_MAX short 类型的最大值
SHRT_MIN short 类型的最小值
USHRT_MAX unsigned short 类型的最大值
INT_MAX int 类型的最大值
INT_MIN int 类型的最小值
UINT_MAX unsigned int 的最大值
LONG_MAX long 类型的最大值
LONG_MIN long 类型的最小值
ULONG_MAX unsigned long 类型的最大值
LLONG_MAX long long 类型的最大值
LLONG_MIN long long 类型的最小值
ULLONG_MAX unsigned long long 类型的最大值

类似地,float.h 头文件中也定义一些明示常量,如 FLT_DIG 和 DBL_DIG,分别表示 float 类型和 double 类型的有效数字位数。表 2 列出了 float.h 中的一些明示常量(可以使用文本编辑器打开并查看系统使用的 float.h 头文件)。表中所列都与 float 类型相关。把明示常量名中的 FLT 分别替换成 DBL 和 LDBL,即可分别表示 double 和 long double 类型对应的明示常量(表中假设系统使用 2 的幂来表示浮点数)。

表 2 float.h 中的一些明示常量

明示常量 含义
FLT_MANT_DIG float 类型的尾数位数
FLT_DIG float 类型的最少有效数字位数(十进制)
FLT_MIN_10_EXP 带全部有效数字的 float 类型的最小负指数(以 10 为底)
FLT_MAX_10_EXP float 类型的最大正指数(以 10 为底)
FLT_MIN 保留全部精度的 float 类型最小正数
FLT_MAX float 类型的最大正数
FLT_EPSILON 1.00 和比 1.00 大的最小 float 类型值之间的差值

程序清单 5 演示了如何使用 float.h 和 limits.h 中的数据(注意,编译器要完全支持 C99 标准才能识别 LLONG_MIN 标识符)。

程序清单 5 defines.c 程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// defines.c -- 使用limit.h和float头文件中定义的明示常量
#include <stdio.h>
#include <limits.h>    // 整型限制
#include <float.h>     // 浮点型限制
int main(void)
{
     printf("Some number limits for this system:\n");
     printf("Biggest int: %d\n", INT_MAX);
     printf("Smallest long long: %lld\n", LLONG_MIN);
     printf("One byte = %d bits on this system.\n", CHAR_BIT);
     printf("Largest double: %e\n", DBL_MAX);
     printf("Smallest normal float: %e\n", FLT_MIN);
     printf("float precision = %d digits\n", FLT_DIG);
     printf("float epsilon = %e\n", FLT_EPSILON);

     return 0;
}

该程序的输出示例如下:

1
2
3
4
5
6
7
8
Some number limits for this system:
Biggest int: 2147483647
Smallest long long: -9223372036854775808
One byte = 8 bits on this system.
Largest double: 1.797693e+308
Smallest normal float: 1.175494e-38
float precision = 6 digits
float epsilon = 1.192093e-07

C 预处理器是非常有用的工具,要好好利用它。

(完)


  1. 注意,在 C 语言中,用 const 类型限定符声明的是变量,不是常量。 ↩︎

comments powered by Disqus