• 第8章 常量
    • 8.1 值替代">8.1 值替代
      • 8.1.1 头文件里的const">8.1.1 头文件里的const

    第8章 常量

    常量概念(由关键字const表示)是为了使程序员能够在变和不变之间画一条界线。这在C++程序设计项目中提供了安全性和可控性。

    自从常量概念出现以来,它就有多种不同的用途。与此同时,常量的概念慢慢地渗回到C语言中(在C语言中,它的含义已经改变)。在开始时,所有这些看起来是有点混淆。在本章里,将介绍什么时候、为什么和怎样使用关键字const。最后讨论关键字volatile,它是const的“近亲”(因为它们都关系到变化)并具有完全相同的语法。

    const的最初动机是取代预处理器#defines来进行值替代。从这以后它曾被用于指针、函数变量、返回类型、类对象以及成员函数。所有这些用法都稍有区别,但它们在概念上是一致的,我们将在以下各节中说明这些用法。

    8.1 值替代

    当用C语言进行程序设计时,预处理器可以不受限制地建立宏并用它来替代值。因为预处理器只做些文本替代,它既没有类型检查概念,也没有类型检查功能,所以预处理器的值替代会产生一些微小的问题,这些问题在C++中可以通过使用const值而避免。

    预处理器在C语言中用值替代名字的典型用法是这样的:

    第8章 常量 - 图1

    BUFSIZE是一个名字,它只是在预处理期间存在,因此它不占用存储空间且能放在一个头文件里,目的是为使用它的所有编译单元提供一个值。使用值替代而不是使用所谓的“不可思议的数”,这对于支持代码维护是非常重要的。如果代码中用到不可思议的数,读者不仅不清楚这个数字来自哪里,而且也不知道它代表什么。进而,当决定改变一个值时,程序员必须进行手工编辑,而且还不能跟踪以保证没有漏掉其中的一个(或者不小心改变了一个不应该改变的值)。

    大多数情况,BUFSIZE的工作方式与普通变量类似;而且没有类型信息。这就会隐藏一些很难发现的错误。C++用const来消除这些问题,具体方法是把值替代移交给编译器。那么可以这样写:

    第8章 常量 - 图2

    这样就可以在编译时编译器需要知道这个值的任何地方使用bufsize,同时编译器还可以执行常量折叠(constant folding),也就是说,编译器在编译时可以通过必要的计算把一个复杂的常量表达式通过缩减简单化。这一点在数组定义里显得尤其重要:

    第8章 常量 - 图3

    可以为所有的内建数据类型(char、int、float和double型)以及由它们所定义的变量(也可以是类的对象,这将在以后章节里讲到)使用限定符const。因为预处理器会引入错误,所以我们应该完全用const取代#define的值替代。

    8.1.1 头文件里的const

    要使用const而非#define,同样必须把const定义放进头文件里。这样,通过包含头文件,可把const定义单独放在一个地方并把它分配给一个编译单元。C++中的const默认为内部连接(internal linkage),也就是说,const仅在const被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个const时,必须赋一个值给它,除非用extern作出了清楚的说明:

    第8章 常量 - 图4

    通常C++编译器并不为const创建存储空间,相反它把这个定义保存在它的符号表里。但是,上面的extern强制进行了存储空间分配(另外还有一些情况,如取一个const的地址,也要进行存储空间分配),由于extern意味着使用外部连接,因此必须分配存储空间,这也就是说有几个不同的编译单元应当能够引用它,所以它必须有存储空间。

    通常情况下,当extern不是定义的一部分时,不会分配存储空间。如果使用const,那么编译时会进行常量折叠。

    当然,想绝对不为任何const分配存储是不可能的,尤其对于复杂的结构。在这种情况下,编译器建立存储,这会阻止常量折叠(因为没有办法让编译器确切地知道内存的值是什么—要是知道的话,它也不必分配内存了)。

    由于编译器不能完全避免为const分配内存,所以const的定义必须默认内部连接,即连接仅在特定的编译单元内;否则,由于众多的const在多个cpp文件内分配存储,容易引起连接错误,连接程序在多个对象文件里看到同样的定义就会“抱怨”。然而,因为const默认内部连接,所以连接程序不会跨过编译单元连接那些定义,因此不会有冲突。在大部分场合使用内建数据类型的情况,包括常量表达式,编译都能执行常量折叠。