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