高质量程序设计指南:C++/C语言
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

5.5 实际应用中如何定义常量

如果要为程序中多个编译单元或者多个模块定义统一的符号常量,在C程序中你常常会把它定义在哪里呢?在C++程序中你又常常会把它定义在哪里呢?如果是只为某一个编译单元定义符号常量,又当如何?

表5-1是我们常用的做法。

表5-1 常用做法

(续表)

显然,在C程序和C++程序中定义符号常量是有区别的。我们仅讨论“多个编译单元公用符号常量”的声明和定义。

在C程序中,const符号常量定义的默认连接类型(Linkage)是extern的,即外连接(external linkage),就像全局变量一样。因此,如果要在头文件中定义,必须使用static关键字,这样每一个包含该头文件的编译单元就会分别拥有该常量的一份独立定义实体(如同直接在每一个源文件中分别定义一次),否则会导致“redefinition”的编译器诊断信息;如果在源文件中定义,除非明确改变它的连接类型为static(实际上是存储类型为static,连接类型为内连接)的,否则其他编译单元就可以通过extern声明来访问它。

但是在C++程序中,const符号常量定义的默认连接类型却是static的,即内连接(internal linkage),就像class的定义一样,这就是在头文件中定义而不需要static关键字的原因。

明白了C和C++符号常量的区别,就不难理解表5-1所列的几种方法了。

这些做法各有优缺点,如果是整型常量或者浮点常量,那么优缺点都不是那么明显。对于C++程序,我们比较一下方法二、四和方法一、三。

表5-2 方法比较

实际上,在大型应用开发过程中,修改常量初值的事情并不会经常发生,甚至极少会发生,即使是在维护阶段,也不太可能变来变去。因此方法二、四较方法一、三的优点就主要体现在存储空间上了。如果是整型和浮点型常量,它们浪费的那点空间对一般应用来说还不至于达到无法容忍的地步,不过对嵌入式应用的开发来说也许是一件大事儿。

那什么常量最浪费空间呢?答案是字符串常量,尤其是较长的字符串常量。

字符串常量的定义和整型常量的定义差不多,但是其类型为const char *,因此我们常常这样定义它们:

const char* const ERR_DESP_NO_MEMORY = "There is no enough memory!";

字符串常量可以在头文件中定义并初始化,也可以在源文件中定义并初始化,但是二者差别较大:

✧ 如果在头文件中定义并初始化,那么包含了该头文件的每一个编译单元不仅会为每一个常量指针常量(const char * const)创建一个独立的拷贝项,而且也会为那个长长的字符串字面常量创建一个独立的拷贝项,就相当于在每一个编译单元内分别定义和初始化每一个常量一次。这是与整型或浮点型常量的定义不同的(它们在初始化完成后不再需要那个字面常量)。因此,每一个编译单元内访问的字符串常量都是它自己单独创建的拷贝。空间的开销就体现在每一个字符串字面常量的独立拷贝项上。

✧ 如果采用方法二,在头文件中声明所有常量指针常量,而在源文件中定义并初始化它们,则每一个包含该头文件的编译单元访问的不仅是常量指针常量的唯一实体,而且字符串字面常量也是唯一的实体。这就大大节约了内存,而且不影响效率。

当然,我们完全可以把常量合并的优化交给编译器和连接器来完成,但是我们还是提倡由自己来优化常量的定义。