Summary
前排提示,本来源于 C++ Primer,Section 2.4,const 的学习记录
一句话总结(看粗体即可,其他辅助理解):
- 对常量的引用,形式是
const int &ri = ci; // ci 是 const int
,细节有三点:- 不允许普通引用绑定常量,即
int &ri = ci; // 错误, ci 是 const int
,因为ri
是普通引用,理论上可以修改所绑定对象,但此处所绑定对象是常量,所以编译器不允许这种行为 - 允许用任何表达式作为初始值,即
const int &ri = 10; // 正确,绑定字面量
或const int &ri = dval; // dval 是 double,正确,绑定其他类型的非常量
,原因详见下文 - 对常量的引用实际含义是,限定引用的操作而不限定引用对象本身,即
const int &r1 = i; r1 = 100; // i 是 int,错误,引用的修改操作不允许
但int &r2 = i; r2 = 100; // 正确,所引用的对象本身可以用其他方式实现修改
,具体含义详见下文
- 不允许普通引用绑定常量,即
- 指向常量的指针(可对比上面对常量的引用类比理解),形式是
const int *pi = ci; // ci 是 const int
,细节有四点:- 记忆方法是从右往左看,即第一个符号是
int *
意味着是一个指针,第二个符号是const
意味着是常量,综合即一个指针指向常量,所以是指向常量的指针。由于底下的数据是常量,所以通过解引来修改底下数据是不允许的,但又由于指针本身是变量,可以改变指向。所以记忆方法大概是const *,可以改变指向但不可解引
- (与对常量的引用同)不允许普通指针指向常量,即
int *pi = ci; // 错误,ci 是 const int
,原因是普通指针pi
有修改常量的风险,编译器因此不允许 - (与对常量的引用不同)初始值只能是相同类型的常量或非常量,即
const int *pi = 10; // 错误,指向字面量
或const int *pi = dval; // dval 是 const double,错误,指向其他类型
- (与对常量的引用同)指向常量的指针实际含义是,限定指针的操作而不限定所指向的对象本身,即
const int *p1 = i; *p1 = 100; // i 是 int,错误,通过指针实现修改操作不被允许
但p1 = &j; // j 是 int,正确,改变指针指向允许
,同时,和上面一样所指向的对象本身也可以以其他形式被改变如int *p2 = &i; *p2 = 100; // 正确,所指向的对象本身可以用其他方式实现修改
- 记忆方法是从右往左看,即第一个符号是
- 常量指针,形式是
int *const p = &i; // i 是 int
,细节有三点:- 记忆方法也是从右往左看,即第一个符号是
const
意味着是一个常量,第二个符号是int *
意味着是指针,综合即一个常量的指针,简称常量指针。含义是指针本身是常量,但底下的数据不关心。所以记忆起来大概是*const,常量指针(不能修改指向)但可以解引
- 只能初始化不能赋值,即
int *const cp = &i; // i 是 int,正确,这是初始化
但初始化后cp = &ci; // ci 是 const int,错误,这是赋值,不允许常量指针重新赋值
- 记忆方法也是从右往左看,即第一个符号是
- 顶层/底层 const 看下图理解,有一点细节:
const int i = 100; const int *const pi = &i;
都属于底层 const
- constexpr,有两点细节:
- 含义是编译阶段就能确定,详见下文
- constexpr 指针属于顶层 const,原因是 constexpr 仅对指针有效而对所指向对象无效
const 和初始化
用一个对象去初始化另一个对象,是不是 const 都没问题:
1 | int i = 10; |
对常量的引用(reference to const)
怎么理解这个概念?看下面两行代码:
1 | const int ci = 1024; // 这叫常量 |
但是,
1 | int &r2 = ci; // 这样就不行,因为 r2 可以修改所引用对象 |
这里还有一个地方值得一提
可能大家都喜欢将 “对常量的引用” 称为 “常量引用”,但严格来说并不对。原因是,引用不是对象,引用只是别名。而且,引用本来就是不可改变其绑定对象的,也即是引用本身就相当于常量
所以,常量引用这种说法可能更倾向于 int &r1, &r2;
这些形式,而对常量的引用则更倾向于 const int &r3 = ci; // ci 是 const int
这种形式
但是,实际上不会分得这么严格,大多时候程序员说的常量引用往往就是 const int &r = ci;
的意思。只不过,本文为了不产生歧义,统一将 const int &r = ci;
形式称为 “对常量的引用”
另外,还有一个特别重要的细节:对常量的引用实际上,只限制引用可参与的操作:
可以这么理解,对常量的引用含义是将引用绑定到常量上,也即是说仅对引用可参与的操作做出限制(不允许修改引用),但对于要引用的对象是什么?是不是常量等方面不作出限定
所以,这个引用绑定的对象其实是有可能被修改的,比如:
1 | int i = 42; |
初始化
只有一条法则:允许用任何表达式作为初始值,如:
允许对常量的引用绑定到非常量上
1 | const int &r1 = 10; // 正确 |
也允许对常量的引用绑定其他类型
1 | double dval = 3.14; |
要理解上面这个法则,首先要了解对常量的引用初始化这个过程发生过了什么:
这就是,初始化分为两步(以上面 const int &r2 = dval;
为例说明)
1 | const int temp = dval; // 编译器使用临时变量完成了类型转换 |
指向常量的指针(pointer to const)
语法上和上面的对常量的引用很像,但是由于引用不是对象而指针是对象,所以总体来说还是有些内容有区别
1 | const int &ri = i; // 对常量的引用 |
先来看类似的部分
首先,也是不允许普通指针指向常量
1 | int *p = ci; // 这样就不行,因为 p 可以修改所指向对象 |
另外,也是一个特别重要的细节:指向常量的指针实际上,只限制指针可参与的操作:
也是一样理解,对常量的引用绑定到常量上,即不允许修改所引用的对象。这里也只对指针可参与的操作作出限定(不允许指针修改对象),但对于指向的对象是不是常量不限制
所以,这个指向的对象也是有可能被修改的,比如:
1 | int i = 42; |
初始化
这里和对常量的引用,可以用任意表达式初始化不同。指向常量的指针只能是相同类型的,常量或非常量才可以用来做初始化
1 | const int &r1 = 10; // 对常量的引用可以使用字面量 |
const 指针(常量指针)
1 | int *const cp = &ci; // cp 是一个常量指针 |
字面意思,指针本身是常量,所以指针不可以改变(也即指针不能改变指向),所以常量指针必须初始化
初始化
既可以用常量对象,也可以用普通对象初始化
1 | int i = 10; |
八股文重灾区之如何记忆
答:从右往左读
1 | const int *p1; |
从右往左第一个符号 int *
是一个指针,第二个符号 const
是常量,合起来就是 “一个指针” “常量”,也就是 “一个指针指向常量”(其中,中间加上 “指向” 两个字来连接)
1 | int *const p2 = &ci; // ci 是 const int |
从右往左第一个符号 const
是常量,第二个符号 int *
是指针,合起来就是 “常量指针”
顶层/底层 const(top-level/low-level const)
直接上图说明指针和 const 的情况
但是,情况换成引用和 const,以下理解是错的
要注意但凡引用加上 const,只能是底层 const(形式上是 const int &ri = ci; // ci 是 const int
)。因为引用这个概念本来就不允许修改,你见过重新赋值的引用吗?所以从这一点来看引用就相当于常量
但是要考虑到引用本身不是对象,它是绑定到其他对象身上作为别名而存在的概念。而指针本身就是个对象,这才有顶层的概念,引用不是对象,去哪里找这个 “顶层”?所以引用加上 const 只能是底层 const
值得一提的是,以下两种形式也属于底层 const
1 | const int ci = 1000; |
因为底层 const 突出的概念是 “底层数据不可修改”,第一个常量不可修改吧,所以算底层 const;第二个虽然看左半部分是顶层 const,但右半部分的含义也是不可修改底层数据,所以实际上也是算底层 const
constexpr
这个限定符就只有一个含义:编译阶段就能确定表达式的值
怎么理解呢?看下面两个函数:
1 | int sum1(int a, int b) { |
用单步调试模式执行上面的代码,你会发现,断点 1 会跳入函数体内,但断点 2 不会跳入函数体内
这是因为,i2
的值早在编译阶段就算出来了,所以执行的时候就不需要进入函数
但要注意,constexpr 的表达式一定要是能够确定才行,如果编译阶段发现表达式确定不下来,那就变成普通函数了
constexpr 和指针
1 | // 这是一个 constexpr 指针 |
对于指针,限定符只限定指针,而与指针所指对象无关
这是说,constexpr 只对指针可参与的操作作出限定(不允许指针修改对象),但对于指向的对象是不是常量不限制————这其实就是 const int *p;
指向常量的指针的性质
也就是说,constexpr 指针是一个顶层 const