const限定符
有时候我们希望定义这样一种变量,它的值不能被改变。为了满足这一要求,可以使用关键字const对变量的类型加以限定。
const int bufSize = 512;
这样就把bufSize定义成了一个常量。任何试图为bufSize赋值的行为都将引发错误。
因为const对象一旦创建后其值不能再改变,所以const对象必须初始化。
默认状态下,const对象仅在文件内有效。
编译器将在编译过程中把用到该变量的地方都替换成对应的值。为了执行这个替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
某些时候我们需要const变量在文件间共享,让这类const对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。
解决办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了。
// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h头文件
exteren const int bufSize; //与file_1.cc中定义的bufSize是同一个
file_1.h头文件中的声明也由extern做了限定,其作用是指明bufSize并非本文件所独有,它的定义将在别处出现。
const的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。
const int ci = 1024;
const int &r1 = ci; //引用及其对应的对象都是常量
r1 = 42; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
对r2的初始化时错误的,假设合法化,则可以通过r2来改变它引用对象的值,这显然是不正确的。
关于引用,引用的类型必须与其所引用的对象的类型一致。但是有两个例外,第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要改表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是一个表达式。
int i = 42;
const int &r1 = i; //允许将const int&绑定到一个普通int对象上
const int &r2 = 42; //正确:r1是一个常量引用
const int &r3 = r1 * 2; //正确:r3是一个常量引用
int &r4 = r1 * 2; //错误:r4是一个普通的非常量引用
这种例外情况的原因可作这样的理解:
double dval = 3.14;
const int &ri = dval;
编译器把上述代码变成了如下形式:
const int temp = dval; //由双精度浮点数生成一个临时的整形常量
const int &ri = temp; //让ri绑定这个临时量
在这种情况下,ri绑定了一个临时量(temporary)对象
当ri不是常量时,下面这个语句C++将归为非法:
int &ri = dval;
如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。而此时绑定的对象是一个临时量而非dval。程序员既然让ri引用dval,就肯定想通过ri改变dval的值,否则为什么要给ri赋值呢?所以,将引用绑定到临时量上,将被视为非法行为。
指针和const
类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。想要存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14; //pi十个常量,它的值不能改变
double *ptr = π //错误:ptr是一个普通指针
const double *cptr = π //正确:cptr可以指向一个双精度常量
*cptr = 42; //错误:不能给*cptr赋值
指针的类型必须与其所指对象的类型一致,但有两种情况例外。第一种情况就是允许令一个指向常量的指针指向一个非常量对象。
const指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定位常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。
把*放在const关键字之前用以说明指针是一个常量,即不变的是指针本身的值,而非指向的那个值。
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip; //pip是一个指向常量对象的常量指针
顶层const
如前所述,指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
用名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是个常量。
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const。
int i = 0;
int *const p1 = &i; //不能改变p1的值,这是一个顶层const
const int ci = 42; //不能改变ci的值,这是一个顶层const
const int *p2 = &ci; //允许改变p2的值,这是一个底层const
const int *const p3 = p2; //靠右的const是个顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const