870920 Menu

SwingCoder之C++备忘录·21

const详解

const体现了C++语言的最小特权原则。编程时要尽可能的多使用const关键字。这样可以进行语义方面的强制约束,等于将排错责任交给编译器,以减轻程序员的压力和负担,便于控制。另外,将数据声明为const类型,还可以提高执行效率(编译器会为const对象提供额外的优化)。

1、全局const对象

这种用法只有一种情况,const常量或对象一旦定义并初始化,在整个程序运行期都无法再有任何改变。const对象在定义时必须初始化。

// 声明并定义全局整型常量,初始化为800。初始化之后,thisWidth的值无法再有任何改变。任何企图给该常量赋值的语句均编译错误。如果不声明为全局常量,可将thisWidth声明为类内的枚举元素
const int thisWidth = 800;

const对象不能调用该类的非const成员函数(非const对象却可以调用const成员函数)。解决方法:类中重载非const版本的成员函数。const引用和指向const对象的指针同理。

2、const与指针

必须明确:任何类型的指针,它本身只是一个整型变量,它在内存空间中所保存的内容(值)只是一个内存地址,即指针所指对象的内存位置。指针所指对象非const的前提下,可以通过指针改变它的值或调用其成员函数,如果指针也非const,则既可以改变它所指的对象,其本身也可指向另一个对象。

指针使用const来修饰,分3种情况(C++内置类型和类类型均同理,此处以类类型为例)。简单说,const出现在“*”号的哪一侧,哪一侧就是定死的,其内容无法被改变。“*”号两侧都有const,则指针和指针所指的对象均为常量,均无法被改变(即不允许通过指针修改它所指向的对象,也不允许该指针指向另一个对象)。

// 指向常量的普通指针。p所保存的地址可以变,即指针可以指向其他堆对象,或将另一个同类型的指针所保存的地址赋值给它。但是,p所指向的对象的内容不能变,即不能通过p来改变它所指的对象:
const 类型 * p = 对象地址或指针;

这种形式还有一种写法,即const写在类型名称之后,“*”号之前。比如:
// 等同于 const 类型 * p = 对象或指针; 不推荐这种写法
类型 const * p = 对象地址或指针;

// 指向变量的常指针。p所保存的地址已定死,即p不能再指向其他对象,同类型的指针也不能赋值给它。但是,p所指的对象可以随意改变,可以通过p来改变所指对象的内容
类型 * const p = 对象地址或指针;

// 指向常量的常指针。p所保存的地址已定死,p所指向的内容也已定死。
// p和所指的对象,谁都不能改变自己的内容。也不能通过p改变它所指对象的内容
// 等同于:类型 const * const p = 对象地址或指针;
const 类型 * const p = 对象地址或指针;

与普通的const对象一致,指向const常量的指针不能调用该类的非const成员函数。

3、const与函数

类的成员函数,声明原型时最多可在3处使用const关键字:
const Component* creatWindow (const String& s) const;

左侧第一个const:该函数返回常量对象。即返回值不能被修改。
中间第二个const:该函数在任何情况下都不能修改该实参的值。

右侧第三个const:该函数为const函数,不能修改本对象的任何数据成员,也不调用本类的非const函数。常函数一是让程序员一目了然,明确该函数不会修改对象的任何数据;二是专供本类的const对象所调用。如果该函数必须要访问并修改该类的某个数据成员,则将该数据成员声明为mutable(不推荐)。const常函数仅存在于某个类之中(全局函数和静态函数没有常函数一说),其实质仍然是约束该函数的行为。注意:每个成员函数均有一个隐含的const参数,而且是1参this,常函数的1参this为指向const的const指针。void func() const; 其实就是:void func(const ThisClass* const this);
const函数在声明和定义时均需显式后缀。const函数中不能调用本类的非const成员函数。

如果一个成员函数无论如何都不会修改类的数据成员,或者与类的数据成员没有任何关系,并且不想让该函数常驻内存,那么就声明为const成员函数,这可以清晰的表明程序员的编码意图。const函数只能被const对象所调用(const对象在任何情况下都不会改变自身的数据成员)。const函数可以有非const的重载版本,适用于被非const对象所调用。为了最大限度符合代码复用的原则,如果两个版本的重载函数逻辑功能差别不大,则非const函数中可以调用const函数,以减少编码量,但反之不行。

另外,全局函数尽管没有常函数一说,但可以返回const数据,形参也可以使用const限定。

4、const数据成员

类内的const数据成员必须且仅能在该类的构造函数的初始化列表中初始化,如果不显式初始化,则编译器调用该对象的构造函数的无参版本进行隐式初始化。无论如何,建议在类的初始化列表中显式初始化类内的所有类类型的数据成员,这样可以避免双重初始化的开销(第一次是构造本类之前的编译器隐式初始化,第二次是构造函数内的赋值语句。指针型数据成员则无需,在构造函数中实例化即可,但依然推荐在构造函数的初始化列表中先将该指针对象置为nullptr,或者直接在初始化列表中new堆对象——如果本类必须使用该指针的有效对象)。

程序运行期间,不能修改类内的const数据成员。const数据成员可被类的const函数所调用,也可被非const函数所调用。但const成员函数中只能读取本类的数据成员,不能修改本类的非const数据成员。

类内的const数据成员如果是其他类的对象(包括栈对象,引用对象和指向常量的指针),即本类聚合或组合了其他类的const对象,则这些对象不能调用它的非const成员函数。这一点须谨记。

注意:上文已提到,类内的const数据成员和引用型数据成员,只能在类的构造函数初始化列表中初始化,这是类的构造函数的特权和主要任务之一。而且,const数据成员和引用型的数据成员一旦初始化之后,整个程序运行期不能再做任何变更(引用型的数据成员,可以通过引用对象修改原值,但不能再引用另一个对象)。

5、const与this指针

类的所有非static性质的成员函数,均有一个隐含的参数:this指针,代表调用此函数的对象。无论类的成员函数是否为const函数,隐含的this指针都是const指针。更清晰的解释:const成员函数所隐含的this指针为指向const对象的const指针,非const成员函数所隐含的this指针则为指向非const对象的const指针。

非const成员函数的this指针(指向变量的const指针):
MyClass* const this // this不能指向其他对象,但可以修改当前所指向的对象的值

而const成员函数的this指针是指向常量的const指针,即:this不能指向其他对象,也不可以修改当前所指向的对象的值,它相当于:const MyClass* const this

由此也可证实:某个类的const对象只能调用该类的const成员函数,而不能调用非const成员函数(因为非const成员函数所隐含的this指针为指向普通对象的常指针,与调用者的const类型不符),而非const对象,既可以调用类的const成员函数,又可以调用类的非const成员函数(非const对象可以赋值给const对象(this指针),反之不行)。

如果类的const成员函数需修改类的某个数据成员,则声明该数据成员时使用mutable前缀,而尽量避免在const成员函数中使用const_cast<MyClass* const>(this)来强转并去除const属性。

6、const的应用场合:

 常量定义(用来取代宏定义和枚举常量),包括类的常量数据成员。

 拷构函数与赋值运算符函数的参数应为const。

 不会被修改的引用型函数参数应为const(不使用const则意味着此函数将修改该参数)。

 不修改本类数据成员的const成员函数(仅供const对象调用)。

 不希望返回值被修改的成员函数或全局函数。