4.3 常量和C预处理器

有时需要在程序中使用常量。例如,可以按照如下形式给出圆的周长:

circumference = 3.14159 * diameter;

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

这里,常量3.14159代表著名的常量pi(π)。要使用常量,只须像本例这样键入一个实际的值即可。不过,有一些强有力的理由可以说服我们使用符号常量来代替这种方法。也就是说,可以使用如下语句,并由计算机在稍后用实际值完成替换:

circumference = pi * diameter;

为什么使用符号常量比较好呢?首先,一个名字比一个数字告诉您的信息多。请比较如下两个语句:

owed = 0.015 * housevalue;

owed = taxrate * housevalue;

如果您在通读很长的程序,那么第二种形式的意义更加清楚。

而且,假设您在多个地方使用了同一个常量,并且必须改变它的值。别忘了,税率是会变动的。那么您只需要改变这个符号常量的定义,而不用在程序中查找出现这个常量的每个地方并做修改。

那么,如何建立一个符号常量呢? 一个方法是声明一个变量,并设置该变量等于所需的常量。您可以这样编写:

float taxrate;

taxrate = 0.015;

这就提供了一个符号名。但taxrate是一个变量,所以您的程序可能意外地改变它的值。幸运的是,C还有两个更好的方法。

C原来就提供了的一个更好的方法是C预处理器。在第2章“C语言概述”中,您已经看到预处理器如何使用#include加入另一个文件中的信息。预处理器也允许定义常量。只须在程序文件的顶部添加如下信息即可:

#define TAXRATE 0.015

当编译您的程序时,值0.015将会在TAXRATE出现的每个地方替代它。这称为编译时代入法(compiletime substitution)。到您运行程序的时候,所有的替代都已经完成了(请参见图4.5)。这样定义的常量通常被称为明显常量(manifest constant)。

阅读 ‧ 电子书库

图4.5 键入的代码与编译的代码

请注意格式:首先是#define,其次是常量的符号名(TAXRATE),接着是常量的值(0.015)(请注意,这个结构中没有使用=符号)。所以一般的形式如下:

#define NAME value

您可以用自己选择的符号名和合适的值来代替NAME和value。没有使用分号是因为这是一种替代机制,而不是C的语句。为什么TAXRATE要大写呢?键入大写的常量是一个明智的C传统。这样,当您在程序中间遇到大写的符号名时,您会立即知道这是一个常量而非变量。大写常量只不过是使程序更易阅读的技术之一。如果您没有大写常量,程序也会照常工作,但是应该培养大写常量的好习惯。

另外还有一个不常用的命名约定,就是在名字前面加上前缀c_或者k_来表示常量,从而得到像c_level或者k_line这样的名字。

符号常量所用的名字必须满足变量命名规则。可以使用大写和小写字母、数字和下划线字符。第一个字符不能是数字。程序清单4.4给出了一个简单的示例。

程序清单4.4 pizza.c程序

阅读 ‧ 电子书库

printf()语句中的%1.2f使输出结果四舍五入为保留两位小数。当然,该程序并不能真正反映您对比萨饼主要关注的方面,但是它确实能说明比萨饼程序设计领域中的一个小问题。下面是一个运行示例:

What is the radius of your pizza?

6.0

Your basic pizza parameters are as follows:

circumference = 37.70, area = 113.10

#define语句也可以用于定义字符和字符串常量。前者用单引号,后者用双引号。下面的例子是正确的:

#define BEEP ‘\a’

#define TEE ‘T’

#define ESC ‘\033’

#define OOPS “Now you have done it!”

请记住,符号名后的所有内容都被用来代替它。不要犯这样的常见错误:

/* 下面的定义是错误的 */

#define TOES = 20

如果您这样做,TOES将会被=20而不是20所代替。如果这样,那么下面的语句:

digits = fingers + TOES;

经转换后会变成下面所示的错误表示方法:

digits = fingers + =20;

4.3.1 const修饰符

C90新增了一种创建符号常量的第二种方法,即可以使用const关键字把一个变量声明转换成常量声明:

const int MONTHS = 12: // MONTHS是一个代表12的符号常量

这就使MONTHS成为一个只读值。也就是说,您可以显示MONTHS,并把它用于计算中,但是您不能改变MONTHS的值。这个新方法比使用#define更灵活;第12章“存储类、链接和内存管理”讨论了该方法以及const的其他用法。实际上,C还有第三种方法可以创建符号常量,那就是第14章“结构和其他数据形式”所讨论的枚举(enum)功能。

4.3.2 系统定义的明显常量

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

#define INT_MAX +32767

#define INT_MIN -32768

这些常量代表int类型的最大和最小可能值。如果您的系统使用32位的int,那么该文件将会为这些符号常量提供不同的值。该文件定义了所有整数类型的最小和最大值。如果您包含了limits.h文件,那么可以使用如下代码:

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

如果您的系统使用4字节的int,那么该系统的limits.h文件就会提供符合4字节整型限制的INT_MAX和INT_MIN的定义。表4.1列出了limits.h中的一些常量。

表4.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类型支持的有效位的个数。表4.2列出了float.h中定义的一些常量(可以使用文本编辑器来打开和查看系统使用的float.h头文件)。本示例与float类型相关。为double和long double类型也定义了相对应的常量,只是常量名中的FLT被DBL和LDBL所代替(该表假设系统用2的幂表示浮点数)。

表4.2 limits.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类型值之间的差值

程序清单4.5示意了如何使用float.h和limits.h中的数据(请注意,许多当前的编译器还不完全支持C99标准,也许还不接受LLONG_MIN标识符)。

程序清单4.5 defines.c程序

阅读 ‧ 电子书库

下面是示例的输出:

Some number limits for this system:

Biggest int: 2147483647

Smallest unsigned 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预处理器是个极其有用的工具,所以在可能的时候要尽量利用它。在本书后面的章节中您会看到更多的相关应用。