const与引用:对常量的引用(reference to const)
const int ci = 0;//常量int对象
int &a = ci;//报错
第二个提示说得很清楚,将 “int &” 类型的引用绑定到 “const int” 类型的初始值设定项时,限定符被丢弃,这是因为引用的类型必须与其所引用对象的类型一致。
结论:非常量引用不能绑定到常量上(第二个提示),无法将“const int”类型转换为“int &”(转换方向从右往左,即第一个提示)。
int i = 0;//非常量int对象
const int ci = 0;//常量int对象
const int &a = i;//指向常量的引用(一般称为常量引用),绑定到非常量
const int &b = ci;//指向常量的引用,绑定到常量
虽然上面提到,引用的类型必须与其所引用对象的类型一致,但也有两种例外情况(本文将讲第一种例外情况):只要等号右边的表达式的结果,能转换为引用的类型。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至一般表达式。
结论:
1)如上两个引用都是常量引用(reference to const,对常量的引用)。
2)如上代码 const int &a = i,可以把int类型转换为const int &类型,即可以把常量引用绑定到非常量对象上,但基本类型得一样。
3)如上代码 const int &b = ci,把常量引用绑定到常量对象,这是正常操作,类型一样其中没有转换过程。
4)如上四个量,只有i可以进行赋值操作,因为只有它是非常量,赋值后,它的引用a的值也随着改变。换句话说,不能通过常量引用a来赋值,但由于a绑定的是非常量i,所以i可以进行赋值操作。
int a = 1;
double &b = a;//报错
const double &c = a;//不报错
从int转double不会丢失数据,但一样这里会产生临时量,临时量是一个prvalue右值,不能绑定给non-const reference,但可以绑定给const reference,同时其生命周期延长至这个const reference的生命周期。
const与指针:指向常量的指针(pointer to const)
const int ci = 0;//常量int对象
int *a = &ci;//报错
结论:与引用一样,非常量指针不能指向常量对象
int i = 1;//非常量int对象
const int ci = 5;//常量int对象
const int *a = &i;//指向常量的指针,指向了非常量
const int *b = &ci;//指向常量的指针,指向了常量
i = 10;
指针的类别必须与其所指对象的类型一致,但有两种例外情况(本文将讲第一种):指向常量的指针,指向了非常量对象。
结论:
1)如上两个指针都是指向常量的指针(pointer to const)。
2)如上代码 const int *a = &i,可以把指向常量的指针,指向非常量对象。
3)如上代码 const int *b = &ci,可以把指向常量的指针,指向常量对象,这是正常操作。
4)如上四个量,只有i可以进行赋值操作,因为只有它是非常量。换句话说,不能通过指向常量的指针a来赋值,但由于a指向的是非常量i,所以i可以进行赋值操作。
常量引用,和指向常量的指针一样,虽然字面上都是说的指向常量,但没有规定其所指对象必须是一个常量。只是因为它们认为自己指向的是常量,所以不能通过常量引用或指向常量的指针,来改变指向的对象的值。但如果指向的对象是非常量,那么这个非常量本身进行赋值操作就是正常操作。
const与指针:指针常量(const pointer)
指针是对象,因此可以把指针本身定为常量。常量指针必须初始化,初始化完成后,它的值(指针存的地址)就不能改变了。
int a = 0;//非常量
int *const ai = &a;//常量指针,指向了非常量
const double pi = 3.14;//常量
const double *const api = π//常量指针,指向了常量
int * const * aii = &ai;//常量指针的二级指针
const double * const * aapi = &api;//常量指针的二级指针
如果只是对常量指针解引用,那么解引用后得到指针指向的对象,根据指向对象为常量或者非常量,来决定常量指针解引用后可不可以赋值操作:
*ai = 1;//可运行
*api = 3.0;//报错,表达式必须是可修改的左值
对指针常量赋值,直接报错
ai = 0;//报错
api = 0;//报错
最后两句,对两个指针常量取二级指针,二级指针的类型就是在一级指针的类型后面加个*。
对aii或者aapi解引用,其实就相当与去掉最后一个*号。
关键点:
- 它是个常量!
- 指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化;
读法是从右往走读,const修饰const左边那个星号,若const左边没有星号,那么就是指的最底层的对象。
二、 常量指针——指向“常量”的指针(const int *p, int const *p)
巧记:常量在指针前面, 程序语言: const在星号之前即可。
定义:又叫常指针,可以理解为常量的指针,指向的是个常量
关键点:
常量指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改;
常量指针可以被赋值为变量的地址,之所以叫常量指针,是限制了通过这个指针修改变量的值;
指针还可以指向别处,因为指针本身只是个变量,可以指向任意地址;
int const* p; const int* p;
常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”。在常量指针中,限制了通过这个指针修改变量的值,指针看起来好像指向了一个常量。用法如下:
int a = 10, b = 20;
const int *p = &a;
p = &b; // 指针可以指向其他地址,但是内容不可以改变
顶层const和底层const(top-level const and low-level const)
对于一般对象来说,其实只有顶层const。而对于指针这种,本身是一个对象,又指向一个对象。所以,指针本身是不是常量,和指针指向对象是不是常量,是两个独立的问题。
用顶层top-level const表示指针本身是一个常量,用底层low-level const表示指针指向对象是一个常量。
引用
用于声明引用的const都是底层const。因为引用本身不是对象,所以不可能有顶层const。
int i = 0;//非常量int对象
const int ci = 0;//常量int对象
const int &a = i;//指向常量的引用(一般称为常量引用),绑定到非常量
const int &b = ci;//指向常量的引用,绑定到常量
指针
*const
代表的是顶层const,指针存的地址不能改变。const int
代表的是底层const,指针指向一个常量,常量自然不能改变。
int i = 0;
int *const p1 = &i;
//不能改变p1指针存的地址,顶层const
const int ci = 42;
//常量不能改变,也算是顶层const
const int *p2 = &ci;
//p2存的地址可以改变,但p2解引用后得到const int,不能改变,底层const
const int *const p3 = p2;
//分析p3类型,*const说明是顶层const,const int说明是底层const
p2 = p3;
在执行对象的拷贝操作时,顶层const不会受影响:
1)const int *const p3 = p2
中,p2没有顶层const,p3有顶层const。
2)p2 = p3
中,执行前,p2没有顶层const,p3有顶层const,执行后,也是一样。
不管有么有执行p2 = p3,各个对象的类型一直都如上图所示,没有变过。
在执行对象的拷贝操作时,两个对象必须具有相同的底层const资格,或者能够转换(一般来说,非常量可以转换成常量,反之不行):
1)int *p = p2; int *p = p3;这两句都会报错,不管p2 p3是不是有顶层const,在拷贝时,都会只看成const int *,但是由于从“const int *”转换到“int *”是不可以的(常量不可以转换为非常量),所以报错。
2)p2 = p3
可运行,p2 p3都是底层const,所以符合“两个对象必须具有相同的底层const资格”。虽然p3还是个常量指针。
3)p2 = &i
可运行,&i后得到一个普通指针int *,可以从“int *”转换到“const int *”,符合“非常量可以转换成常量”。
int i = 0;
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
constexpr
constexpr即为const expression常量表达式。用于声明constexpr的类型一般为字面值类型literal type。指针、引用也属于字面值类型,但指针和引用定义成constexpr时,它们的初始值必须受到限制。 在编译期确定(不全是,变量一定是,函数不一定)。
一个constexpr指针的初始值必须是一个存储在固定地址的对象,或者是nullptr或0。
一般来说,函数体内定义的变量不是存放在固定地址中的。但函数也允许定义一类有效范围超过函数自身的变量,这类变量也有固定地址。一个constexpr指针只能指向这些变量。
constexpr设置变量本身为顶层const:相当于在星号后面加个const。
const int *p = nullptr;
constexpr int *q = nullptr;
constexpr const int *k = nullptr;
auto
auto自行推断类型,忽略顶层const,保留底层const。如果想用auto还保留顶层const,在auto前面加const即可,但只对非复合类型起作用。
const int i = 42;
auto j = i;
const auto j2 = i;
const auto &k = i;
auto &k2 = i;
auto *p = &i;
const auto *p2 = &i;
return 0;
const auto &k = i,使得i成为常量。
k2已经能推断出const int &,所以const auto &k = i前面的const没有起作用。
p已经能推断出const int *,const auto *p2 = &i前面的const看似应该使得指针成为一个常量指针,但并没有起到作用。