高质量程序设计指南c++/c语言(21)--实际应用中如何定义常量
如果要为程序中多个编译单元或者多个模块定义统一的符号常量,在C程序中会把它定义在哪里呢?在c++程序中又会定义在哪里呢?如果是只为某一个编译单元定义符号常量,又当如何?
显然,在c和c++中定义符号常量是有区别的。我们仅仅讨论“多个编译单元公用符号常量”的声明和定义。
在c程序中,const符号常量定义的默认连接类型时extern的,即外连接的。因此,如果要在头文件中定义,必须使用static关键字,这样每一个包含该头文件的编译单元就会分别拥有该常量的一份独立定义实体(如同直接在每一个源文件中分别定义一次),否则会导致redefinition的错误。如果在源文件中定义,除非明确改变它的连接类型为static的,否则其他编译单元就可以通过extern来访问它。
但是在c++程序中,const符号常量定义的默认连接类型却是static的,就像class的定义一样,这就是在头文件中定义而不需要static关键字的原因。下面讨论c++的情况。
方法1:
头文件中const int MAX = 1024; //有无static都行
然后每一个使用它的编译单元#include该头文件中即可
方法2:
头文件中声明extern const int MAX;
并且在某个源文件中定义一次并初始化const int MAX = 1024;
然后每一个使用它的编译单元#include该头文件中即可
方法3:
如果是整型常量,在某个公用头文件中定义enum类型,然后每一个使用它的编译单元#include该头文件中即可
方法4:
定义为某一个公用类的static const数据成员并初始化,或者定义为类内的枚举类型
//Utility.h
class Utility { public: static const int MAX; enum { OK = 10 }; };
//Utility.cpp
const int Utility::MAX = 1024;
//每一个使用它的编译单元#include该类的定义即可
#include <iostream> #include "Utility.h" using namespace std; int main(void) { int data = Utility::MAX; int a = Utility::OK; return 0; }
(1)方法一、三
优点:维护方便
缺点:如果修改常量初值,则将影响多个编译单元,所有受影响的编译单元必须重新编译。而且每个符号常量在每一个包含了它们的编译单元中都存在一份独立的拷贝,每个编译单元访问的是各自的拷贝内容,因此浪费存储空间。
(2)方法二、四
优点:节约存储,每一个编译单元访问的都是这个唯一的定义。修改初值后只需要重新编译定义所在的编译单元即可,影响面很小。
缺点:如果要改变初值,需要修改源文件。
那什么常量最浪费空间呢?答案是字符串常量,尤其是较长的字符串常量。例如const char * const str="hello world"。字符串常量可以在头文件中定义并初始化,也可以在源文件中定义并初始化,但是两者的区别比较大。
(1)如果在头文件中定义并初始化,那么包含了该头文件的每一个编译单元不仅会为每一个常量指针常量创建一个独立拷贝,而且也会为那个长长的字符串常量创建一个独立的拷贝。
(2)如果在头文件中声明常量指针常量,而在源文件中定义并初始化。则每一个包含该头文件的编译单元访问的不仅是常量指针常量的唯一实体,也是字符串字面常量的唯一实体。这就大大节约了空间,而且不影响效率。
当然,我们完全可以把常量合并的优化交给编译器和连接器来完成,但是我们还是提倡自己来优化常量的定义。
在c++程序中,const符号常量定义的默认连接类型却是static的,就像class的定义一样
class的连接类型的确是static的,看一下面的例子
1、main.cpp
#include<iostream> using namespace std; class A { public: int m_data; }; extern void f(A &a); int main(void) { A a; f(a); //必然出错 return 0; }
2、h1.cpp
class A { public: int m_data; int aa; }; void f(A &a) { a.aa = 10; }