C++ 中关于 const 的知识点很多,在这里做个总结。
const 关键字修饰的变量在创建后值就不能改变了,因此必须在创建时进行初始化。
对象的类型决定了能够在对象上进行哪些操作。对 const 对象来说,只能使用那些不能改变对象状态的操作。
如果 const 对象是由一个编译时常量(compile-time constant)进行初始化的:
const int bufSize = 512; // input buffer size
那么编译器一般会在编译时用这个常量替换该 const 变量。即编译器把所有用到 bufSize 的地方替换为512。为了进行这种替换,显然编译器需要知道 const 变量的初值是多少,这就需要检查 const 变量是如何被定义并初始化的。
如果程序分布在多个文件中,为了对变量的值进行替换,编译器在编译每个用到该 const 变量的文件时,都需要知道该 const 变量是如何被定义的,这就要求在每个用到该 const 变量的文件中都对该变量进行定义(define 而不是 declare),然而一个变量是不能被多次定义的。为解决这个矛盾,规定 const 变量的作用域是文件本身(local to file)。当我们在多个文件中定义名字相同的多个 const 变量时,就像在每个文件中定义了一个不同的变量一样。
(对于一般变量来说只能在一处进行定义,在其他文件中使用时需要使用 extern 关键字进行声明。)
有时我们想在多个文件中共享一个 const 变量,但是该 const 变量的初始化器(initializer)并不是一个常量表达式(constant expression,常量表达式的值在编译时就可以被确定,如一个数字或算术表达式,一个通过 constant expression 进行初始化的 const 变量也是一个 constant expression)。在这种情况下,我们不希望编译器在每个用到该 const 变量的文件中都单独生成一个新的变量。我们希望在一个文件中进行定义,在其他文件中进行声明。要达到这一目标,需要在定义和声明时同时使用 extern 关键字(否则就是 local to file)。
// file_1.cc
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize;
下面用代码具体演示一下。如果是 nonconst 的变量,则只能在一处进行定义。
// file_1.cc
#include <cstdio>
int bufSize = 512;
void print();
int main() { print(); return 0; }
// file_2.cc
#include <cstdio>
int bufSize = 100;
int print() { printf(" %d ", bufSize); return 0; }
编译时报错:multiple definition of bufSize'
。如果将两个 bufSize 中的一个改为 const,或两个都改为 const,则编译通过,输出 100。
// file_1.cc
#include <cstdio>
const int bufSize = 512;
void print();
int main() { print(); return 0; }
// file_2.cc
#include <cstdio>
extern const int bufSize;
int print() { printf(" %d ", bufSize); return 0; }
如果是这样,file_1 中进行定义时没有加 extern 关键字,则编译时报错:undefined reference to bufSize'
。
如果将两个文件中的 const 关键字都去掉,或者给 file_1 中的 const 变量也加上 extern 关键字,编译成功,输出512。
对 const 变量的引用
像普通变量一样,也可以将一个引用绑定到一个 const 变量。对 const 变量的引用同样不能改变该 const 变量的值。
const int ci = 1024;
const int &r1 = ci; r1 = 42; // 错误,不能通过对 const 的引用改变 const 变量的值
int &r2 = ci; // 错误,不能用 nonconst reference 引用 const 变量
指针,const 和类型别名(type aliases)
考虑下面的代码:
typedef char *pstring; const pstring cstr = 0; const pstring *ps;
const 修饰的是 pstring,而 pstring 的类型是指向 char 的指针,所以 const pstring 是一个 constant pointer to char,而非 pointer to const char。
不能通过直接替换来理解类型别名:
const char *cstr = 0; // 这是对 const pstring cstr 的错误理解
const 参数
进行赋值时,top-level const 会被忽略,所以以下两个函数定义不能同时存在:
void fcn(const int i) {}
void fcn(int i) {}
这两个函数能够接收同样的参数,所以不算重载。
可以用 nonconst 对象对 low-level const 对象进行初始化,但不能用 low-level const 对象初始化 nonconst 对象。(low-level const 指针认为其指向的对象是 const 对象)。
int i = 42;
const int *cp = &i; // ok: cp 是 low-level const,可以用 nonconst 进行初始化,但不能通过 cp 改变 i 的值
const int &r = i; // ok: reference to const,不能通过 r 改变 i 的值
const int &r2 = 42; // ok
int *p = cp; // error: 不能用 low-level const 初始化 nonconst,但可以用 const_cast 强制转换
int &r3 = r; // error
int &r4 = 42; // error: 不能通过 literal 初始化 plain 的 reference
如果可能就应该使用 reference to const
不用 reference to const,会让调用者错误地认为函数会改变参数的值。而且不用 reference to const,会限制能够传递的参数类型。不能向普通的 reference 传递 const 对象,不能传递 literal,不能传递需要转换的类型。
const 成员函数
成员函数的参数列表后面可以加关键字 const,作用是修饰 this 指针。
this 指针默认是 const pointer to nonconst object,故不能指向 const object,也就是说 const 对象的方法无法被调用。将成员函数变为 const 成员函数,其 this 指针隐含参数变为 const pointer to const object。const 成员函数不能改变对象状态。const 对象只能调用 const 成员函数。
注意 const 成员函数如果返回 *this,那么返回的是一个 const 对象,不能在返回对象基础上继续调用 nonconst 的成员函数。为解决这一点可以基于成员函数的 constness 对成员函数进行重载。