c++系列文章(4):const限定符

初始化

  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; //正确,非常量能够转换成常量