• 8.4 类
    • 8.4.1 类里的const">8.4.1 类里的const

    8.4 类

    本节介绍const用于类的两种办法。程序员可能想在一个类里建立一个局部const,将它用在常数表达式里,这个常数表达式在编译期间被求值。然而,const的意思在类里是不同的,所以为了创建类的const数据成员,必须了解这一选择。

    还可以使整个对象作为const(正如刚刚看到的,编译器总是将临时类对象作为常量)。但是,要保持类对象为常量却比较复杂。编译器能保证一个内建类型为常量,但不能控制类中的复杂性。为了保证一个类对象为常量,引进了const成员函数:const成员函数只能对于const对象调用。

    8.4.1 类里的const

    常数表达式使用常量的地方之一是在类里。典型的例子是在一个类里建立一个数组,并用const代替#define设置数组大小以及用于有关数组的计算。数组大小一直隐藏在类里,这样,如果用size表示数组大小,就可以把size这个名字用在另一个类里而不发生冲突。然而所有的#define从定义的地方起就被预处理器看成是全局的,所以用#define就不会得到预期的效果。

    读者可能认为合乎逻辑的选择是把一个const放在类里。但这不会产生预期的结果。在一个类里,const又部分地恢复到它在C语言中的含义。它在每个类对象里分配存储并代表一个值,这个值一旦被初始化以后就不能改变。在一个类里使用const意味着“在这个对象生命期内,它是一个常量”。然而,对这个常量来讲,每个不同的对象可以含有一个不同的值。

    这样,在一个类里建立一个普通的(非static的)const时,不能给它初值。这个初始化工作必须在构造函数里进行,当然,要在构造函数的某个特别的地方进行。因为const必须在建立它的地方被初始化,所以在构造函数的主体里,const必定已被初始化了。否则,就只有等待,直到在构造函数主体以后的某个地方给它初始化,这意味着过一会儿才给const初始化。当然,无法防止在构造函数主体的不同地方改变const的值。

    8.4.1.1 构造函数初始化列表

    在构造函数里有个专门初始化的地方,这就是构造函数初始化列表(constructor initializer list),起初用在继承里(继承将在第14章介绍)。构造函数初始化表列表(顾名思义,只出现在构造函数的定义里)是一个出现在函数参数表和冒号后,但在构造函数主体开头的花括号前的“函数调用列表”。这提醒人们,表里的初始化发生在构造函数的任何代码执行之前。这是初始化所有const的地方,所以类里的const的正确形式是:

    8.4 类 - 图1

    开始时,上面显示的构造函数初始化列表的形式容易使人们混淆,因为人们不习惯把一个内建类型看成好像也有一个构造函数。

    8.4.1.2 内建类型的“构造函数”

    随着语言的发展以及人们为使用户定义类型看起来像内建类型一样所作的努力,有时似乎使内建数据类型看起来像用户定义类型更好。在构造函数初始化列表里,可以把一个内建类型看成好像它有一个构造函数,就像下面这样:

    8.4 类 - 图2

    这在初始化const数据成员时尤为关键,因为它们必须进入函数体前被初始化。

    我们还可以把这个内建类型的“构造函数”(仅指赋值)扩展为一般的情形,这就是为什么要在上段代码中加入float pi(3.14159)定义的原因。

    把一个内建类型封装在一个类里以保证用构造函数初始化,这是很有用的。例如,下面是一个Integer类:

    8.4 类 - 图3

    8.4 类 - 图4

    在main()中的Integer数组元素都自动地初始化为零。与for循环和memset()相比,这种初始化并不必付出更多的开销。很多编译器可以很容易地把它优化成一个很快的过程。