文章目录
- 1 普通引用
- 1.1 对普通值的普通引用
- 1.2 对常量值的普通引用
- 1.3 对普通指针的普通引用
- 1.4 对常量指针的普通引用
- 1.5 对指针常量的普通引用
- 1.6 对指向常量的指针常量的普通引用
- 2 常量引用
- 2.1 对普通值的常量引用
- 2.2 对常量值的常量引用
- 2.3 对普通指针的常量引用
- 2.4 对常量指针的常量引用
- 2.5 对指针常量的常量引用
- 2.6 对指向常量的指针常量的常量引用
- 3 总结
这标题,是不是听着就头大?常量(const)、引用、指针本来就 C++ 里面三个很头疼的概念,现在把他们合到一起,更想不明白了。没关系,看完本文后,你一定会对这三个概念有更清晰的认识。
首先我们分析分析,这三个合到一起,共能产生多少种不同的组合?一个对象可以是非指针类型(姑且称为一个“值”),也可以是指针类型。其中值可以是普通值,也可以是常量值;指针可以是普通指针、常量指针(指向常量,指针本身可变),指针常量(指针不可变,指向的值可变)、指向常量的指针常量(指针和指向的值都不可变)。所以一个对象可以细分为6种。而引用也有两种,一种是普通引用,一种是常量引用。这样算下来,应该是 6 × 2 = 12 6\times2=12 6×2=12 种组合了。其中标题“常量引用绑定到指向常量的常量指针”是最复杂的组合。下面我们就逐个讲清楚这些组合,如何定义,有哪些特点。
为不失严谨性,作出以下两点说明:
-
有人说多于12种组合,因为指针可以指向另一个指针,引用也分左值引用和右值引用。没错,但我们这里不考虑多级指针,并只考虑左值引用。
-
有人说少于12种组合,因为一些组合并不合法,无法定义。也没错,但我们不妨称之为“非法组合”。
1 普通引用
1.1 对普通值的普通引用
int i = 1;
int &ri = i;
这是最简单的。ri
就是对普通值 i
的一个普通引用。ri
可以看作 i
的一个别名:我们给函数传 ri
就是在传 i
,修改 ri
的值就是在修改 i
的值。
1.2 对常量值的普通引用
const int ci = 1;
int &rci = ci; // 非法
这是非法的。我们不能把一个常值绑到一个非常量引用上。理由是,如果可以绑定,则可以通过 rci
修改 ci
,而 ci
是常值,这不就矛盾了吗。因此 C++ 规定其为非法。
1.3 对普通指针的普通引用
int i = 1;
int *p = &i;
int *&rp = p;
这是第一个复合类型。怎么理解 rp
的类型名很关键。我们按照由内向外做如下解读:
rp
也是 p
的别名,用 rp
时就是在用 p
。例如通过 rp
可修改 i
的值。
*rp = 10;
std::cout << i << '\n'; // 输出10
1.4 对常量指针的普通引用
int i = 1;
const int *cp = &i;
const int *&rcp = cp;
这次类型名解读和1.3节中的类似,唯一区别是这次指针指向一个 const int
对象。正是这一区别,是我们无法通过 rcp
改变 i
的值了,正如无法通过 cp
改变 i
的值一样。
值得一提的是,因为此例中 i
不是 const int
,所以我们仍然可以改变 i
的值,此时 *cp
和 *rcp
的值也随之变化。
i = 2;
cout << *cp << ' ' << *rcp << '\n'; // 输出2 2
所谓常量指针指向一个常量,仅是指针认为它指向常量,至于实际到底指向什么,常量还是非常量都是可以的。我们称之为指针被欺骗(见2.1节)。如果我们把一开始就把 i
定义成 const int
类型的,就使 cp
变成指向一个真正的常量的指针了。
1.5 对指针常量的普通引用
int i = 1;
int *const pc = &i;
int *&rpc = pc; // 非法
这是第二个非法组合。原因类似,因为指针常量 pc
是顶层 const,我们无法把一个普通的引用绑定到顶层 const上,否则就能通过 rpc
修改一个常量 pc
了,矛盾了。
1.6 对指向常量的指针常量的普通引用
int i = 1;
const int *const cpc = &i;
const int *&rcpc = cpc; // 非法
这是第三个非法组合,原因同上。
普通引用的六种组合到这里就讲完了,总结一下,非法的三种组合都是自己本身就是常量的,而普通引用禁止绑定到常量上,常量只能被常量引用绑定。这就来到下一节了:常量引用。
2 常量引用
2.1 对普通值的常量引用
int i = 1;
const int &cri = i;
把常量引用绑定到普通值上,就好比让常量指针指向一个非常量,都是虚假的绑定/指向,我们可以戏称之为:引用/指针被欺骗了。这里常量引用以为自己绑到了常量上,所以拒绝通过 cri
来修改 i
;但是实际上 i
不是常量,所以可以通过 i
自己修改 i
的,此时 cri
的值也跟着变。
i = 3;
std::cout << cri << '\n'; // 输出3
2.2 对常量值的常量引用
const int ci = 1;
const int &crci = ci;
这才是真正的绑到一个常量上了。这儿我问大家一个问题:对“常量”值进行“常量”引用的时候,明明有两处“常量”,可为什么语句里面只看到一个 const
关键字?可能懂的人觉得这问题很stupid,但是在写一些复杂的声明的时候,真有可能犯迷糊。提示:看看1.2节,对普通值进行常量引用时,语句里有几个 const
关键字?再看看2.1节,对普通值常量引用的语句和本节的声明引用的语句有何不同?
2.3 对普通指针的常量引用
int i = 1;
int *p = &i;
int *const &crp = p;
这个声明引用的语句也有点意思,我们解读一下。
可以看到,这也是个被欺骗的引用。我们不能改 crp
的值,因为它以为自己绑的是常量,不过,它以为它以为的就是它以为的?我们还是可以改 p
,此时 crp
跟着变。另一方面,虽然我们不能改 crp
,但我们可以改 *crp
,因为 crp
指向一个普通的 int
变量。
*crp = 9;
cout << i << '\n'; // 输出9
crp = nullptr; // 非法
p = nullptr; // 合法,p和crp都变成空指针
2.4 对常量指针的常量引用
int i = 1;
const int *cp = &i;
const int *const &crcp = cp;
这个声明和2.3的基本相似,唯一区别在于指针指向的是一个 const int
对象。造成的影响就是不能再改 *crcp
了,但 crcp
仍是被欺骗的引用,因为它绑的是底层 const,而非顶层 const。
*crcp = 9; // 非法
crcp = nullptr; // 非法
cp = nullptr; // 合法,cp和crcp都变成空指针
2.5 对指针常量的常量引用
int i = 1;
int *const pc = &i;
int *const &crpc = pc;
这个引用的声明和2.3节非常像,大家要注意区分,还是思考那个问题,为什么对指针“常量”进行“常量”引用,只出现一个 const
?
我们不能修改 crpc
,也不能修改 pc
。我们可以修改的是 *crpc
,用它来修改 i
是可以的。
*crpc = 3;
cout << i << '\n'; // 输出3
2.6 对指向常量的指针常量的常量引用
他来了他来了,最复杂的它终于来了!
int i = 1;
const int* const cpc = &i;
const int* const &crcpc = cpc;
以上是对声明引用的解读,有了这个解读,是不是就清晰多了。从这么多常量就可以看出来了,它自己不能、也不能通过它进行任何形式的修改。
3 总结
普通引用 | 常量引用 | |
---|---|---|
普通值 | int &ri = i; | const int &cri = i; |
常量值 | int &rci = ci; 非法 | const int &crci = ci; |
普通指针 | int *&rp = p; 可以通过 rp 修改原值,可以修改 rp | int *const &crp = p; 可以通过 crp 修改原值,不能修改 crp 。 |
常量指针 | const int *&rcp = cp; 无法通过 rcp 修改原值,可以修改 rcp | const int *const &crcp = cp; 不能通过 crcp 修改原值,不能修改 crcp 。 |
指针常量 | int *&rpc = pc; 非法 | int *const &crpc = pc; 可以通过 crpc 修改原值,不能修改 crpc |
指向常量的指针常量 | const int *&rcpc = cpc; 非法 | const int* const &crcpc = cpc; 不可以通过 crcpc 修改原值,不能修改 crcpc |
完结撒花~