彻底搞懂 C++ 中 顶层 const 和 底层 const
写在前面
最近在项目中使用 const 关键字 时遇到了一些困惑, 重新琢磨了一下 C++ 中 const 关键字的使用,对于顶层 const 和 底层 const 重新学习后又有了一点新的理解,在此记录一下。
顶层 const 与 底层 const
概念
- 顶层 const:可以修饰所有数据类型,表示该类型的对象本身是常量
- 底层 const:与指针和引用类型有关,表示地址所指向的对象是常量
示例
int i = 10;
const int ii = 11; // 顶层const: 对象 ii 本身是常量
const int *a = &i; // 底层const: 指针对象 a 本身不是常量,而所存地址指向的对象是常量
int const *b = &i; // 底层const: 与上一条语句写法不同,但都是表示指向常量的指针,即指针常量
int* const c = &i; // 顶层const: 指针对象 c 本身是常量,即常量指针
const int* const d = &i; // 左侧const是底层const,右侧const是顶层const
const int& e = i; // 底层const: 引用所存地址指向的对象是常量
我相信首次尝试理解 顶层 const 和 底层 const 概念的同学 看了上述的例子对于如何区分二者可能已经有点感觉了,但针对一些细节可能还存在一些疑惑,接下来我会针对一些关键点展开讲讲。
上述程序可访问 godbolt 执行测试
常量指针 与 指针常量
第一个问题我们来讲讲 常量指针、指针常量 与 顶层 const、底层 const 联系。
概念
- 常量指针:即指针本身是常量,表示指针存储的地址不可被修改
- 指针常量:即指向常量的指针,表示其所指向的对象不可被修改
对应关系
对比 顶层 const、底层 const的概念,我们可以发现,在 const 修饰指针变量时:
顶层 const 等价于 常量指针
底层 const 等价于 指针常量
例子
int i = 10;
int* const a = &i; //底层 const => 指针常量
const int* b = &i; //顶层 const => 常量指针
const int* const c = &i; //指向常量对象的常量指针,既有底层 const 又有 顶层 const
方便记忆(感谢指出已修订):
const 在 * 号 右侧的为 底层 const => 指针常量 => 指针存储的地址不可被修改
const 在 * 号 左侧的为 顶层 const => 常量指针 => 指针指向的对象不可被修改
引用 与 const
第二个问题我们来讲讲 引用 中 使用 const 关键字。
如果对引用陌生虽然不影响下文阅读但也建议去补习一下
语法
在引用中使用 const 关键字只能按照如下语法使用:
int i = 10;
const int& a = i; //底层const 引用所存地址指向的对象是常量
或许大家对其为什么是底层 const 会有所困惑,下面讲讲我的理解:
int i = 10;
const int& a = i; // const int& a => const (int* const) a
对于引用来说我们可以将其视为 常量指针,因此变量 a 实际上可以看做 int* const
类型的数据,再在其上使用 const 进行修饰即得到了 const int* const
数据类型,因此此新增的 const 为 底层 const
在使用中只需记住:引用中使用的 const 一定是底层 const
类型转换
上面讲了这么多可能觉得不算难理解,但不知道为什么要区分顶层 const 和 底层 const,那么接下来就来看看其实际用途,首先看一下下面的代码是否能编译通过
int i = 10;
const int ii = 11;
i = ii; // (1)
const int* a = &i; // (2)
const int* b = ⅈ // (3)
int* const c = &i; // (4)
int* const d = ⅈ // (5)
const int* const e = &i; // (6)
const int* const f = ⅈ // (7)
const int* g = c; // (8)
const int* h = e; // (9)
int* const j = a; // (10)
int* const k = e; // (11)
int* const l = c // (12)
int* m = c // (13)
上述程序可访问 godbolt 执行测试
编译过后会发现 (5),(10),(11),无法编译通过,是不是很困惑,记住下面的公式
左值(无 const || 顶层 const) = 右值(无 const || 顶层 const) => 可转换
左值(底层 const) = 右值(无 const || 顶层 const || 底层 const) => 可转换
左值(无 const || 顶层 const) = 右值(底层 const) => 不可转换
接下来我们一行行分析一下上述代码验证一下上面公式的正确性:
int i = 10;
const int ii = 11;
i = ii; // (1) i 无 const, ii 有顶层 const, 可转换
const int* a = &i; // (2) &i 为 int*, a 有底层 const, &i 无 const, 可转换
const int* b = ⅈ // (3) &ii 为 const int*, b 有底层 const, &ii 有底层 const, 可转换
int* const c = &i; // (4) &i 为 int*, c 有顶层 const, &i 无 const, 可转换
int* const d = ⅈ // (5) &ii 为 const int*, d 有顶层 const, &ii 有底层 const, 不可以转换
const int* const e = &i; // (6) &i 为 int*, e 有底层 const, &i 无 const, 可转换
const int* const f = ⅈ // (7) &ii 为 const int*, f 有底层 const, &ii 有底层 const, 可转换
const int* g = c; // (8) g 有底层 const, c 有顶层 const, 可以转换
const int* h = e; // (9) h 有底层 const, e 有底层 const, 可以转换
int* const j = a; // (10) j 有顶层 const, a 有底层 const, 不可以转换
int* const k = e; // (11) k 有顶层 const, e 有底层 const, 不可以转换
int* const l = c // (12) l 有顶层 const, c 有顶层 const, 可以转换
int* m = c // (13) m 无 const, c 有顶层 const, 可以转换
写在最后
看到这里恭喜你对于 const 的理解与运用有上了一个台阶,上面的内容是我学习 const 的一个总结,里面包含了一些个人的理解,可能在某些部分有失偏颇,有问题欢迎私信或者评论区讨论。