初始化
const对象一旦创建后其值就不能再改变,所以const对象必须初始化,且初始值可以是任意复杂的表达式。
const int i = getsize(); //运行时初始化
const int j = 1; //编译时初始化
当以编译时初始化的方式定义一个const对象时,编译器将在编译过程中把用到该变量的地方替换成对应的值。为了执行替换,编译器必须知道常量的初始值,因此默认情况下,const对象被设定在仅在文件内有效,当在多个文件中出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。
某些时候,我们不希望编译器为每个文件分别生成独立的变量,而是希望像普通变量一样在不同文件之间共享const变量,也就是说只在一个文件中定义const,而在其他多个文件中声明并使用它。解决的方式是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次即可。
//file_1.cc
extern const int bufSize = fcn();
//file_1.h
extern const int bufSize; //与file_1.cc中定义的bufSize是同一个
const和引用
说明:程序员常把对const的引用简称为“常量引用”。
之前提到过,引用的类型必须与其所引用对象的类型一致,但有两个例外。第一种例外就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能够转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值、甚至一个一般表达式。
int i = 1;
const int &r1 = i; //正确,允许将常量引用绑定到一个普通变量上
const int &r2 = 1; //正确,允许将常量引用绑定到一个字面值上
const int &r3 = i*2; //正确,允许将常量引用绑定到一般表达式上
int &r4 = i*2; //错误,r4是普通的非常量引用,不能绑定到一个表达式上
double d = 3.14;
const int &r5 = d; //正确,double变量能够转换成int类型
为什么能够将一个普通double类型变量绑定到一个int类型的const引用上?实际上,当一个常量引用被绑定到另外一种类型上时,这个常量引用实际是绑定在了一个临时变量上。
const int temp = d; //由double数生成一个临时的int变量
const int &r5 = &temp; //r5实际是绑定在这个临时变量上
常量引用只是对引用可参与的操作做出了限定,对与引用的对象本身发是不是一个常量并未限制。因此常量引用既可以绑定const对象也可以绑定非常量对象,如果绑定的是非常量对象,则只是不能通过引用改变该对象,但可以直接通过该对象进行改变。
int i = 1;
const int &r = i; //正确,常量引用可以绑定到非const对象上
r = 2; //错误,不能通过常量引用改变绑定对象的值
i = 2; //正确,i是非const对象,可以改变其值
const和指针
指向常量的指针
和常量引用类似,指向常量的指针不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。
const int i = 1;
int *p1 = &i; //错误,p1是一个普通指针,不能存放常量对象的地址
const int *p2 = &i; //正确,只能用指向常量的指针存放常量对象的地址
*p2 = 2; //错误,指向常量的指针不能用于改变其所指对象的值
之前提到过,指针的类型必须与其所指向对象的类型一致,但有两个例外。第一种例外就是允许一个指向常量的指针指向一个非常量对象。
int i = 1;
const int *r = &i; //正确
和常量引用类似,指向常量的指针只是规定不能通过指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
常量指针
指针是对象而引用不是,因此指针也能像其他类型一样,将指针本身定义为常量。常量指针必须初始化,而且一旦初始化,则它的值(也就是存放在指针中的那个地址)就不能改变了。把*放在const之前用以说明指针是一个常量。
int i = 0;
int *const p1 = &i; //p1将一直指向i
const int j = 0;
const int *const p2 = &j; //p2是一个指向常量的常量指针
为了区分指针的const的属性,用顶层const表示指针本身是一个常量,用底层const表示指针所指的对象是一个常量。
当执行对象的拷贝时,常量是顶层const还是底层const区别明显。对于顶层const而言,由于拷贝操作不会改变被拷贝对象的值,因此拷入或者拷出的对象是否是常量都没有影响。如果是底层const,则拷入和拷出的对象必须具有相同的底层const属性,或者两个对象的数据类型必须能够转换,一般来说非常量能够转成常量,反之则不行。
const int i = 0;
const int *p1 = &i;
int *p2 = p1; //错误,p1包含底层const属性,而p2则不包含
int j = 0;
int *p3 = &j;
cosnt int *p4 = p3; //正确,非常量能够转换成常量