C++中的const关键字深入理解(关于引用指针顶层const)

Posted 思过崖下碧水剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中的const关键字深入理解(关于引用指针顶层const)相关的知识,希望对你有一定的参考价值。

基本用法

在编写C++代码时,我们时常会想要定义一种不会被改变的变量,这时就要使用const关键字来进行限定。
const关键字是constant的缩写,意思是恒定不变的,有read-only属性。要让一个变量不发生改变,那么显然要在创建时就给它规定好初始值,因此const变量必须要手动进行初始化。const关键字最重要的作用就是限制变量的值不被改变,因此const变量可以参与运算,初始化其他变量,只要本身的值不被改变就好。值得一提的是,const对象默认只在文件内有效,如果需要在多个文件之间共享一个const对象,必须要在变量的定义之前添加extern关键字。

关键点

  • const作用于指针时,有限制指向对象类型和限制指针本身的含义上的区别,也即是顶层与底层const的区别,需要仔细辨别。
  • 由于引用不是对象无法被const限制本身,所以与引用有关的const都是限制其绑定关系。
  • const在数据类型前限制绑定(指向)关系,const在后限制对象本身。

const与引用

const拥有不可改变的特性,C++之中还有一种定义之后就无法随意改变的关系,那就是引用的绑定关系。一个引用在定义时必须初始化绑定一个已有的对象,而这种绑定关系一旦确定之后也不能更改,从这个角度看来所有的引用绑定关系都算是一种常量。
将引用绑定到常量上时和其他对象一样,例如const int var = 18; const int &r = var;意思是所定义的引用是一种绑定const int的变量,虽然引用的绑定关系本身就带有常量的性质,但定义引用时指明所绑定的变量类型,与引用本身的常量性质并不冲突。const变量本身无法被修改,那么当然也不能通过引用来修改。
刚才说到了定义引用时要指明绑定的类型,一般情况这个指明的类型都要和即将绑定的对象类型一致,但也有两个例外,其一就是绑定类型为常量的引用(简称常量引用)可以绑定任意能转换成该引用类型的表达式(也即用该表达式初始化),其二则是基类引用可以绑定到派生类对象上,与本次探讨的const内容无关所以不在此叙述。
我们首先应当明确,绑定类型为非const的引用是不能绑定const变量的,因为这样没有意义且不合法。反过来说const限定绑定类型的引用可以绑定到非const的变量,这就是上面所说的例外。例如int i = 42; const int &r = i;这样是正确的,上面所说能转换成引用类型的表达式都能初始化引用,所以double d = 3.14; const int &r = d;也是正确的,此种情况编译器会用一个const临时变量进行类型转换再将引用绑定到此临时量上。这种引用绑定的例外的意义就在于限制了一种不能改变i的方式来获取i的数值,而还可以通过其他方式比如直接访问i或者用非const的引用绑定i来修改它的数值。
注意:引用不是一种对象,所以不能用const限制引用本身,如上面提到的常量引用指的是引用的绑定对象类型为常量,若对引用本身做const限制则不合法。如int& const r;const在后不合法。

const与指针

指针与const的关系和引用十分相似。简单来说,如果你想要绑定或者指向一个const对象,那么你本身也需要是一个const型的引用或指针,否则就非法且无意义(对一个无法改变的量设定一种能改变值的访问方式显然不合适)。const int i = 17; int* p = &i;就不合法。
指针类型与指向对象类型不一致也有两种例外,其一是允许指向常量的指针指向非常量对象,其二是基类指针可以指向派生类。这里讨论第一类例外,注意到这里的限制比引用严格,常量引用允许绑定任意能转换成该类型的表达式,而指针必须指向同类型的,放宽的限制只是常量可以指向非常量。
例如double d = 3.14; const double* p = &d;这样合法,这里const在前限制的是指向对象为常量,只是不能通过p改变d的值。
另一方面,当const对引用或者指针对象本身进行限定时,限定的就是引用的绑定关系和指针的指向关系,常量指针必须初始化。可以这样想象,const限定好像是指针对象与指向对象之间的一条铁链,这条铁链拥有const属性不可改变,正因此常量指针必须初始化,即一开始就要拴好东西,而铁链拴着的另一头的变量盒子里装的东西并不受const限定,如果指向的非常量那么就可以使用指针进行修改。
int v = 17; int* const r = &v; *r = 10;例如这三条代码,const在后限制的指针本身而非指向的对象类型,因此*r的值可以被改变。而若是 int v = 17;const int* r = &v;const在前,限制的是指向的对象类型为常量,*r就不能被修改。

顶层const

上面的关键点指出了指针本身是常量和指针所指的对象是常量是两个互相独立的问题。有专门的名词来对此进行清楚的区分。

  • 顶层const表示指针本身是常量,一般在指针类型之右(后)。

  • 底层const表示指针所指对象是常量,一般在指针类型之前(左)。

  • 引用不是对象,只有底层const。

  • 对指针、引用之外的对象来说,const都是顶层,习惯放在数据类型前。

  • 记忆:指针顶本后,底指前

举例:

int i = 0;
int* const p1 = &i; 	//指针顶本后
const int ci = 2;	//变量顶层const
const int* p2 = &ci;	//指针底指前
const int* const p3 = p2;	//前面的为底层,后面的为顶层
const int& r = ci;	//引用只有底层

在使用时要注意的区别就是底层const执行拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型能转换,一般是非常量转换成常量。

int* p = p3;	//错误,底层const不匹配
p2 = p3;	//正确
p2 = &i;	//正确,int*可转换为const int*,指针的第一个例外
int& r = ci;//错误
const int& r2 = i;//正确,const int&可绑定int,还记得引用的第一个例外吗

constexpr

C++11规定可以使用constexpr生命变量来由编译器验证该变量的值是否为常量表达式。
常量表达式是指值不会改变,编译中即可计算结果的表达式。一个表达式要是常量表达式,要满足数据类型为常量,同时由常量表达式初始化。
字面值(算数类型、引用和指针,不包括string)属于常量表达式。如果constexpr声明了一个指针,相当于一个顶层const,且初值必须是nullptr、0或者指向固定地址对象。

与const比较理解的话可以认为const是运行期常量,实际是"只读"的意思,constexpr 是编译期常量,是值类型。

以上是关于C++中的const关键字深入理解(关于引用指针顶层const)的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C++中的mutable关键字

深入理解C++类和对象(下)

小白学习C++ 教程八在C++指针传递引用和Const关键字

小白学习C++ 教程八在C++指针传递引用和Const关键字

C++杂谈const限定符与const指针

C++类中的常成员和静态成员