解决关于顶层const与底层const中的主要疑惑点——基于《C++Primer》

一、问题背景


本文主要针对《C++Primer》一书中,关于顶层const与底层const的相关描述中,就自己所遇到的一些疑惑,以及最终自己的一些理解,提供经验分享。


二、预备知识背景


1. 指针常量

例如int* const pconst紧挨着p,表示其对p的限定,而p是一个int*类型的指针,所以我们说这个指针(地址)不能再进行改变,所以称为指针常量。

2. 常量指针

例如const int *qconst紧挨着*q,表示对*q的限定,而*q是指针q所指向的内容,所以说,这个指针(地址)所指向的内容不能再进行改变,所以称为常量指针。

备注

  • 在《C++Primer》中,删去了指针常量这一概念,而用常量指针来表述,上面提到的指针常量,即地址不变。并且使用指向常量的指针来表述上述的常量指针,即指针指向的内容不变。这是完全是国内编程界与书本中的一些名词的解释上的区分,只需要理解它的本质即可,并不影响我们对书本的阅读。
  • 网络上可能存在一些说法,说int const *p中的const是对指针的p的修饰,int *const p中的const是对p所指向的内容的修饰,这只是一种说法上的区别,也可以说是一种记忆上的差别,这并不会导致对指针常量或者常量指针定义的差别化,大家阅读时不用过分纠结。

三、相关定义

3.1、狭义上

  • 顶层const

表示指针本身是个常量。——《C++Primer》

  • 底层const

表示指针所指向的对象是一个常量。——《C++Primer》

3.2、广义上

  • 顶层const

更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。——《C++Primer》

  • 底层const

更一般的,任意的对象所指向的内容或者是任意对象所绑定的内容是一个常量,而修饰这个任意对象的const就称为底层const 。——个人见解


3.3、定义说明

3.3.1、狭义定义的狭义性

  • 狭义上的定义,之所以称为狭义,是因为在针对const修饰的常量例如const int i时,我们不方便按照定义对其进行解释,事实上我们都知道这个常量前面的const是一个顶层const(原因见后文),因为按照狭义定义,“表示指针本身是一个常量”,这句话咋一看有点无头无脑,实际上,它表述的含义是:const修饰指针时,如果此时限定的是指针(地址)本身不能进行更改,即指针常量,那么const就是一个顶层const显然根据这个定义,我们似乎只能对对象(变量)为指针的声明进行描述,那么对于const int i,这样一个普通的整型常量,显然i并不是一个指针,那么上述的两个狭义上的定义就对其不适用了。
    于是,在书本上就出现了广义上更一般的说法进行进一步补充,即上述的广义定义。

3.3.2、广义定义的普适性

  • 顶层const

更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。——《C++Primer》

  • 底层const

更一般的,任意的对象所指向的内容或者是任意对象所绑定的内容是一个常量,而修饰这个任意对象的const就称为底层const 。——个人见解

以上附上定义,方便阅读。
下面我们使用广义定义对三个例子进行解释说明,来验证其普适性。这里顺便一提,我们将对象当成变量的别称即可,方便理解。

  • const int i

此处i是变量,也就是定义中的对象,所以任意对象是常量。const int i符合定义,所以其是,顶层const


  • const int *p

此处p是变量,即对象,任意对象所指向的内容,那么就是指针p所指向的内容,即*p是一个常量的话,那么该const就是一个底层const。由于const修饰的是*p,所以const int*p是一个常量指针,即*p是一个常量。综上,const int *p中的const是一个底层const


  • int * const p

此处p是变量,即对象,根据顶层const定义,任意对象是常量,即指针p是常量的话,那么就能证明该const是顶层const,显然const直接修饰p,即指针变量,指针p无法更改满足定义,故该const是顶层const。而该指针所指向的内容并没有进行限定,所以无法得知任意对象所指向的内容是否为常量,所以不满足底层const的条件。


阶段总结
以上的三个例子很好的说明了广义定义的普适性和唯一性,所以我们使用该定义去判断const的类型是可行的。

四、引用const是底层const

4.1、问题引出

这句话出现在书中的一句代码的注释中,该部分代码如下:

int i = 0;
int *const p1 = &i;
const int ci = 42;
const int *p2 = &ci;
const int *const p3 = p2;
const int &r = ci;//用于声明引用的const都是底层const

就是这样一句不起眼的注释,却告诉了我们对于修饰引用的const直接就是底层const,这比给我们一个定义可方便多了。在读到这句话的时候,我也是一头雾水,知道总结出上面的广义底层const定义之后,我再对这句话进行解释,发现是完全适用的,不过这需要插入一个小知识点之后才能进行说明。


4.2、知识点引入

常量引用的对象可以是非常量。——《C++Primer》

这个知识点我相信大家应该没有什么疑问,一般来说,引用的类型和与之绑定的对象的类型严格匹配。但是书上给出了我们两个例外,第一个就是常量引用,下面举例:

int val = 42;
const int &r = val;//r是一个常量引用,其对象是val.

显然,常量引用r的对象的类型是int类型的变量,而r的类型是const int类型,这是不匹配的,按照严格匹配的原则,也应该是const int val = 42。但是前面说了常量引用是个例外,它允许其对象是非常量,所以此处对象是val这个变量也是可行的,当然这不是我们需要探讨的关键,我们需要引入为什么能够实现这一例外的知识点。


  • 编译器对上述代码进行了优化,其优化过程如下:
const int temp = val;
const int &r = temp; 

通过创建一个临时整型常量去接收val这个整型变量,非常量是可以通过自动类型转换赋值给常量类型的,但是反之不行。所以temp成功接收,然后使用temp这个整型常量作为一个整型常量的引用的对象,这样就间接的实现的严格匹配。这个转换机制就是我们需要引入的知识点。

4.3、回归正题

利用广义定义解释
“用于声明引用的const都是底层const”


  • 底层const

更一般的,任意的对象所指向的内容或者是任意对象所绑定的内容是一个常量,而修饰这个任意对象的const就称为底层const 。——个人见解

解释
const int &r = val
任意对象即r,其所绑定的内容,即对象val,是否是一个常量,如果是那么说明该const是一个底层const。显然,如果没有引入4.2节中的知识点,即转换机制。那么是无法解释的,因为常量引用的对象可以是非常量,这是一个很明确的知识点,而此处所绑定的对象val是一个变量,那么是否就说明不满足底层const的定义呢?答案是其实还是满足底层const的定义的,因为引用r绑定的对象其实并不是变量val,而是转换机制中的临时量const int temp.这个临时量是一个常量。这也刚好切合了底层const的广义定义。

附带说一句
正是由于绑定的是临时变量temp而不是变量val,所以,常量引用是不能通过引用对其对象(val)进行修改的,因为根本就没绑定到val。修改的只是temp,编译器肯定是不希望发生这样的事的,所以就认定为非法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值