初始化
const对象一旦创建后其值就不能再改变,因此const对象必须初始化。当以编译时初始化的方式定义一个const对象时,编译器将在编译过程中把用到该变量的地方都替换成对应的值:
const int bufSize = 512; // 代码中所有用到bufSize的地方都会用512代替
const的引用
可以把引用绑定到const对象上,称为对常量的引用,对常量的引用不能用作修改它绑定的对象:
const int ci = 1024;
const int &r1 = ci; // 正确,引用及其对应的对象都是常量
r1 = 42; // 错误,r1是对常量的引用
int &r2 = ci; // 错误,试图让一个非常量引用指向一个常量对象
初始化常量引用时允许用任意表达式作为初始值,允许为一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式:
int i = 42;
const int &r1 = i; // 允许将const int&绑定到一个普通int对象上
const int &r2 = 42; // 允许将const int&绑定到字面值
const int &r3 = r1 * 2; // 允许将const int&绑定到一个一般表达式
int &r4 = r1 * 2; // 错误,r4是一个非常量引用
一般的引用必须与其所引用的对象类型一致,而常量引用的例外情况可以从编译器角度出发来理解,当常量引用被绑定到另一类型上时,比如:
double dval = 3.14;
const int &ri = dval;
此处ri是一个int型常量引用,但dval是double类型,为了让ri绑定到一个整数,编译器会创建一个临时变量:
const int temp = dval; // temp取dval的整数部分
const int &ri = temp; // ri绑定到临时变量temp
由于常量引用不允许对所引用的对象做更改,因此常量引用绑定到不同对象是允许的;对于非常量引用来说,若允许非常量引用绑定到不同对象上,由于实际上是绑定到一个临时变量,这样无法通过引用修改绑定对象的值,因此C++将这种语法归为非法。
const引用可绑定到非const对象
int i = 42;
int &r1 = i; // r1绑定到对象i
const int &r2 = i; // 常量引用r2绑定到对象i,但不能通过r2修改i的值
r1 = 0; // 正确,可通过非常量引用修改对象的值
r2 = 0; // 错误,不能通过常量引用修改对象的值
指针和const
指向常量的指针(指针常量)
指向常量的指针不能用于修改其所指对象的值,但可以修改指针本身的值,其值可以是一个非常量对象(仍不能用于修改其所指对象的值),因为指针本身并不是一个常量。
const double pi = 3.14;
const double *ptr = pi; // ptr是一个指向常量的指针
*ptr = 4.14; // 错误,ptr不能用于修改其所指对象的值
double dval = 3.14;
ptr = &dval; // 正确,可以给ptr赋值指向非常量对象,但不能通过ptr修改对象的值
const指针(常量指针)
把const放在*之后说明指针是一个常量,即不变的是指针本身的值而不是所指对象的值:
int num = 0;
int *const ptr = # // ptr是常量,只能指向对象num,可通过ptr修改num的值
const int *const ptr2 = # // ptr2是常量,且不能通过ptr2修改num的值
顶层const
顶层const可以表示对象是变量,底层const则与指针和引用等复合类型的基本类型部分有关。引用只有底层const。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行:
int i = 0;
const int *const p = &i; // 常量指针可指向非常量对象,但不能通过p修改i的值
int *p2 = p; // 错误,没有相同的底层const类型
const int *p3 = p; // 正确,有相同的底层const类型,且int *const能赋值给int *(常量能赋值给普通变量)