2.4 常量
常量是在程序运行过程中值不发生改变的数据。
在C程序中,为了能给变量直接赋初值或用数值参与运算,经常需要使用由各种数码组成的不同进制的数据(如整数、实数等)以及字符、字符串等,这类数据通常能直接从其字面形式即可判别其类型,称为字面常量,或称为直接量。如1,20,0,-6为整数, 1.2,-3.5为实数,‘a’,‘b’为字符,“C语言”为字符串等。
还有一种情况是C程序中可能会多次使用同一个数值,比如常数π,与其每次书写时都写上3.141 592 65,不如用一个标识符来代替该数值,即标识符常量,有时又称为符号常量。由于标识符总比数值常量本身更具意义,因而在程序中使用标识符常量不仅可以提高程序的可读性,而且在代码中修改常量也极为方便,并有助于预防程序出错。
下面来介绍各种不同类型的常量。
2.4.1 整数
整数,即没有小数点的数,由于可以有不同进制,因而为了让编译器能识别,需要按下列规则来书写:
(1)对于十进制整数,直接书写其数码,如34,128等。
(2)对于八进制整数,在书写数码前要以数字0开头,如045,即(45)8,表示八进制数45,等于十进制数37;-023,即(-23)8,表示八进制数-23,等于十进制数-19。注意:八进制的数码是0,1,2,3,4,5,6,7。
(3)对于十六进制整数,在书写数码前要以0x或0X开头,如0x7B,即(7B)16,等于十进制的123,-0X1a,即(-1a)16,等于十进制的–26。注意:十六进制的数码是0~9,A~F(a~f)。
需要说明的是,为了能使编译器知道程序中指定的整数是一个具体的整数类型,还可在一个整数中添加类型后缀(它们是由类型名的首字母构成的),其规则如下:
(1)以L或其小写字母l作为后缀的整数表示长整型(long)整数,如78L,496l, 0X23L,023l等都是合法的长整数。
(2)以U或u作为后缀的整数表示无符号(unsigned)整数,如2100U,6u,0X91U, 023u等都是合法的无符号整数。
(3)以U(或u)和L(或小写字母l)的组合作为后缀的整数表示无符号长整型(unsigned long)整数,如23UL,23ul,23LU,23lu,23Ul,23uL等都是合法的无符号长整数。
(4)默认时,如果一个整数没有添加后缀,则可能是int或long类型,这取决于该整数的大小。
2.4.2 实数
实数即浮点数,为了让编译器能识别它们,在书写时应遵循下列规则:
(1)对于十进制实数,由于它们在形式上和十进制整数的区别是小数点,因此在书写实数时必须有且仅有一个小数点,但不允许出现单独一个小数点。例如,0.12,.12,1.2, 12.0,12.,0.0都是合法的实数。
(2)对于指数形式的实数,为了能区别十进制整数和实数,C语言引入特征符E或e,用来表示科学计数法中的10,并强调E或e后面的指数必须是整数。例如,1.2×109,则应写成1.2e9或1.2E9。由于引入特征符E或e后,指数形式的实数在书写格式上已与十进制整数和实数不一致了,故特征符E或e前面的数字是否有小数点并不重要,但E或e前面必须有数字,否则以E或e字母开始的实数会被编译器优先识别成一个合法的标识符。简单地说,字母E或e前必须有数字,且E或e后面的指数必须是整数。
同样,若要指定实数的具体类型,还必须在实数后面指定类型后缀符:
(1)F或f后缀来表示单精度浮点数(float)。例如,1.2f,1.2E5f等都是合法的单精度浮点数。
(2)指定L或小写字母l后缀来表示长双精度浮点数(long double),若没有后缀,则表示默认的双精度浮点数(double)。例如,1.2L是合法的长双精度浮点数,而1.2默认是double型。
注意:在书写各种形式的整数或实数时(包括后缀),整个常量中不能出现空格,否则将因空格是词法中的分界符而导致编译器对词义的解释出现不同的结果。例如,1.2 f,编译器会解释成“1.2”和“f ”这两个词,从而使“f ”标识符因未先定义而变成不合法的。
由于阅读时,书中的空格难以看出,故用符号表示一个空格,本书做此约定。
2.4.3 字符常量和转义字符
在C语言中,用一对单引号来作为区分字符与整数、实数的特征符。这就是说,用一对单引号括起来的字符称为字符常量或直接称为字符,如‘B’,‘b’,‘%’,‘ ’等都是合法的字符,但若只有一对单引号‘ ’,一般作为空字符处理。需要说明:
(1)字符在计算机内是将其编码值以整型格式来存储的,由于大小写字母的编码值不同,因此是两个不同的字符。例如,‘B’和‘b’是两个不同的字符。
(2)由一对单引号指定的字符通常是指单字节字符,例如‘AB’,‘语’,‘12’等都不合法。
(3)由于在程序代码中,编码值大于127的扩展ASCII码字符无法直接输入,为了能使用这些字符,C语言允许通过使用“\”引导符,并指定1~3位八进制数或X(包括小写x)后跟1~2位十六进制数来表示相应编码值的字符。例如,‘\101’和‘\x41’都是表示编码值为65的字符‘A’;若为‘\0’,则表示ASCII码值为0的字符,这样的字符称为空字符。
在C语言中,含有“\”引导符的字符还有很多,例如,前面程序中的‘\n’,它代表按Enter键换行,而不是表示字母n。这种将反斜杠“\”后面的字符转换成另外意义的方法称为转义表示法,‘\n’称为转义字符。“\”称为转义字符引导符,单独使用没有任何意义。若要表示反斜杠字符,则应为‘\\’。表2.2列出了常用的转义字符。
表2.2 常用转义字符
在这些转义字符中,除了前面表示相应编码值字符的作用外,有的是将C语言本身特征符转化成原来的一般字符含义,如‘\'’(单引号)、‘\"’(双引号)、‘\\’(反斜杠)和‘\?’(问号)等;有的是用来转换成输出格式的操作(以后还会讨论),如‘\r’(回车)和‘\t’(水平制表)等。需要强调:
(1)不是每个以转义法表示的字符都是有效的转义字符,当C编译器无法识别时,就会将该转义字符解释为原来的字符。例如:‘\A’和‘\N’等虽都是形式上合法的转义字符,但却都不能被C编译器识别,此时‘\A’就是‘A’,‘\N'就是‘N’。
(2)注意0,‘0’和‘\0’的区别:0表示整数,‘0’表示数字0字符。‘\0’表示ASCII码值为0的字符。
2.4.4 字符串常量
在C语言中,字符串常量是由一对双引号括起来的字符序列,简称字符串。字符串常量中除一般字符外,还可以包含空格、转义序列符或汉字等其他字符。例如:
“Hello, World!\n” “C语言”
等都是合法的字符串常量。字符串常量包含的字符个数称为字符串长度。若仅有一对双引号“ ”,则这样的字符串常量的长度为0,称为空字符串。
书中表示字符串的一对双引号“ ”是汉字字符,在程序代码中是不可以的,它们只能用"来表示;类似地,一对单引号‘’也是汉字字符,在程序代码中也只能用'表示。
显然,在字面常量中,双引号是字符串常量用于区分其他数值常量的特征符,如果需要在字符串中出现双引号,则必须用转义字符‘\"’来替换表示。例如:
“Please press \"F1\"to help!”
这个字符串被解释为
Please press "F1" to help!
要注意字符串的机内格式与整型、实型和字符型等数据类型有着本质的区别。对于数值来说,其数据类型确定了它所占内存空间的大小。也就是说,当操作这些数值时,它们所需的内存空间的大小也是确定的。而字符串则不然,由于不同的字符串所包含的字符个数不一样(每个ANSI字符都要占1字节),因而不同字符串所需的内存空间的大小也各不相同。正因为如此,C语言才没有也无法有字符串的基本数据类型。
那么怎样才能确定字符串操作时所需内存空间的大小,并保证字符串在内存空间中存取的正确性呢?
不同高级语言对上述问题的解决有着不同的方式,C语言采用以null为结尾的方式。其中null是指空字符,等价于‘\0’,即值为0的字符。也就是说,C语言对于字符串存储操作,是将字符串中的字符依次存放在内存空间,并在其后再存入一个‘\0’字符。同时将‘\0’字符作为字符串所占内存空间的结束标志,称为字符串的结束符。当字符串从内存空间中依次提取时,首先判断取出的字符是否为结束符,若是,则字符串提取结束,从而保证了字符串存取的正确性。
可见,字符‘a’和字符串“a”有着本质区别:存储时,字符‘a’仅占1字节,“a”则占2字节,因为“a”除了字符a需要1字节外,字符串结束符‘\0’还需1字节,如图2.6所示。
图2.6 “a”和‘a’的区别
需要说明:
(1)由于C语言字符串的存储格式是以‘\0’为结尾的,也就是说,若在字符串中指定‘\0’,则这样的字符串的长度和字节大小各为多少呢?例如,“AB\CD\t\0\n”。
显然,当字符串存入内存时,系统会将其所包含的所有的字符连同结束符‘\0’一起依次存放,每个ASCII字符占1字节,需要8字节的内存空间,如图2.7所示。但提取时,由于先遇到第一个‘\0’字符,C语言就会将其视为结束标志,提取出来的字符串是第一个‘\0’前的字符序列,即“AB\CD\t”,显然该字符串的长度(字符个数)为5。
图2.7 字符串存储
(2)要注意字符串“AB\CD\t\0\n”中的转义特征符‘\’,它必须与后跟的字符构成转义字符,而不论构成的转义字符是否是有效的转义字符。例如,字符串中的‘\C’就是一个字符,千万不要将‘\’和‘C’看做两个字符。
2.4.5 标识符常量
与变量相似,标识符常量在使用前同样需要先进行声明。在C语言中,标识符常量包括const修饰的只读变量、#define定义的常量及enum类型的枚举常量等3种形式。考虑到以后还要对enum类型的枚举常量进一步讨论,故这里仅讨论#define定义的标识符常量以及const修饰的只读变量。
1.#define标识符常量
在C语言中,允许程序用编译预处理指令#define来定义一个标识符常量。例如:
#define PI 3.14159265
这条指令的格式是#define后面跟一个标识符,再跟一串字符,中间用空格隔开。由于它不是C语言的语句,因此行尾没有分号。
在程序编译时,编译器首先将程序代码中所有“PI”这个词用“3.14159265”来替换,然后再进行代码编译,故将#define称为编译预处理指令。#define又称为宏定义命令,上述定义的PI称为宏名。
【例Ex_PI.c】 用#define定义符号常量
#include<stdio.h> #include<conio.h> #define PI 3.14159 int main() { double r=100.0,area; area=PI*r*r; printf("The area of circle is %lf\n", area); return 0; }
程序运行的结果如下:
The area of circle is 31415.900000
需要说明的是,#define定义的常量不是真正的标识符常量,因为在编译预处理完成后,标识符PI的生命期也就结束了,不再属于程序中的元素名称。而且,编译器本身不会对标识符后面的内容进行任何语法检查,仅仅在程序中与标识符进行简单替换。例如:
#define PI 3.141MNP+59
虽是一个合法的定义,但它此时已经失去了一个标识符常量的作用。正因为如此, ANSI/ISO C建议标识符常量都使用const来定义,而不使用#define。
另外,#define作为预处理命令,还有一些较为复杂的应用,以后还会进一步讨论。
2.const只读变量
在定义变量时,可以使用关键字const来修饰,这样的变量是只读的,即变量所绑定的内存空间中的内容是不可修改的,但在程序中对其可以读取数据。由于不可修改,因而它是一个标识符常量,且在定义时必须初始化。需要说明的是,通常将标识符常量中的标识符写成大写字母以与其他标识符相区别。例如:
const float PI=3.14159265f; /* 指定f使其类型相同,否则会有警告错误 */
因π字符不能作为C语言的标识符,因此这里用PI来表示。PI被定义成一个float类型的只读变量,由于float变量只有存储7位有效位的精度,因此PI的实际值为3.141593。
若将PI定义成double,则全部接受上述数字。事实上,const还可放在类型名之后,如下列语句:
double const PI=3.14159265;
这样,就可在程序中使用PI这个标识符常量来代替3.14159265。例如,将例Ex_PI.c中的语句:
#define PI 3.14159
改为
const double PI = 3.14159265;