1. 常量引用(Reference to const)
1.1 定义与使用
定义一个引用时,可以把它变为 常量引用(const
reference),表示“通过这个引用,不能修改所绑定对象的值”。示例:
const int ci = 1024;
const int &r1 = ci; // r1 是对常量 ci 的引用
r1 = 42; // 错误:不能通过 r1 修改 ci
如果我们尝试给 r1
赋值,将会产生编译错误,因为 r1
是个常量引用。
1.2 绑定非 const
对象、字面值或临时值
在 C++ 中,有一个重要的“特例”:常量引用可以绑定到 非 const
对象、字面值,甚至临时表达式。例如:
int i = 42;
const int &r2 = i; // 可以绑定到普通 int 变量
const int &r3 = 42; // 可以绑定到字面值
const int &r4 = i * 2; // 可以绑定到表达式(产生临时值)
- 上例中,
r2
是“对常量的引用”,意味着不能通过r2
修改i
。但i
本身并不变成常量,它仍可以通过其他方式被修改。 - 当绑定到字面值或表达式结果时,编译器通常会生成一个 临时量(temporary) 并让这个
const
引用指向该临时量,以保证“引用”的合法性。
为什么非 const
引用不能绑定到字面值或临时值?
如果是一个普通引用(非 const
),就可以通过它改写所绑定的对象值。但该对象在这种情况下实际是个临时量,修改它并不能改变原始对象(因为原始对象是个不同的东西,甚至是字面值),这会产生对程序员“错觉”的可能。因此 C++ 语言直接禁止了非 const
引用绑定临时量,避免潜在的混淆和错误。
1.3 常量引用并不一定指向常量对象
常量引用仅意味着“你不能通过这个引用修改所绑定对象”。它并没规定那个对象本身一定是不可变的。比如:
int i = 42;
int &r1 = i; // 普通引用
const int &r2 = i; // 常量引用
r2 = 0; // 错误:不能通过 r2 修改 i
r1 = 0; // 合法:通过 r1 修改 i 的值
在这个例子里,i
仍可以被其他普通引用或 i
本身来修改,只是不能用 r2
来改而已。
2. 指针与 const
2.1 指向常量的指针(Pointer to const)
与常量引用类似,指向常量的指针 表示我们不能通过此指针来修改所指对象的值。例如:
const double pi = 3.14;
// double *ptr = π // 错误:ptr 是普通指针,不能指向 const 对象
const double *cptr = π // 正确:cptr 指向一个常量
*cptr = 42; // 错误:不能通过 cptr 修改 pi
就像常量引用一样,“指向常量的指针”并不要求其目标确实是一个常量对象。它仅禁止“通过该指针来修改目标”。因此,下述代码也是合法的:
double dval = 3.14;
const double *cptr2 = &dval; // 合法:cptr2 是 pointer to const
*cptr2 = 6.28; // 错误:不能用 cptr2 改 dval
dval = 6.28; // 合法:直接改 dval
在这个例子里,dval
并不是一个常量。但是通过 cptr2
我们无法改动 dval
;从别的途径或直接对 dval
赋值却依然可以。
2.2 常量指针(Const Pointer)
由于指针是一个对象,它可以本身被声明为 const
。这称为“指针本身是常量”。它有以下特征:
- 指针的值(所存储的地址)不可改变;
- 指针所指的对象是否可写,还要看对象是否是
const
。
语法形式通常是把 const
放在 *
之后,但阅读时最好从变量名向外看:
int errNumb = 0;
int *const curErr = &errNumb; // curErr 是一个常量指针,指向 int
// curErr 本身的值不能再改了,即 curErr = &anotherInt; 是错误的
*curErr = 10; // 合法,curErr 指向一个 int 非常量,可改 errNumbconst double pi = 3.14159;
const double *const pip = π // pip 是指向常量 double 的常量指针
// pip 自身是 const,不可更改指向;指向的对象也是 const,不可被修改
-
int *const curErr
的含义:- 最近的关键字是
const
,说明指针本身是常量; *
表明这是一个指针;- 声明的基本类型是
int
,表明指针指向int
非常量。
- 最近的关键字是
-
const double *const pip
则是一条“指向const double
的常量指针”。也就是说:pip
不可重新指向别的对象;- 也不能通过
pip
修改其所指对象(pi
)。
3. 组合示例
有时我们会看到声明中有多个关键字 const
和多个 *
叠加,就需要仔细解析语义。
例如:
// p1 - 指向一个 const int 的普通指针
const int *p1 = /*...*/;// p2 - 一个 const 指针,指向 int(可变)的地址
int *const p2 = /*...*/;// p3 - 一个 const 指针,指向 const int
const int *const p3 = /*...*/;
p1
:能改p1
指向哪里,但不能通过p1
改目标数据;p2
:不能改p2
本身的指向,但能通过p2
改目标数据;p3
:不能改p3
的指向,也不能改目标数据。
掌握了这三种形式,就掌握了指针与 const
组合的大部分用法。
4. 小结
- 常量引用(
const &
)- 不能通过此引用修改所绑定对象;
- 可绑定到非常量对象、字面值及临时量。
- 指向常量的指针(
const T*
)- 无法通过该指针修改目标对象;
- 目标对象是否真实是个常量无关紧要,只要使用此指针时不可改。
- 常量指针(
T *const
)- 指针本身的地址固定,不能改指向;
- 至于能否改指向对象,取决于对象是否本身是
const
。
- 指向常量的常量指针(
const T *const
)- 既不能改指针本身,也不能改目标对象,是最严格的指针限定。
正确地运用 const
限定符可以提高程序的可读性与安全性。当别人(或自己稍后)阅读到你的代码时,可以从 const
中准确推断出哪些值是不可变、哪些指针的指向是不可变,从而避免许多潜在的错误与混淆,也使代码逻辑更清晰。
参考资料
- C++ 标准文档或 cppreference(可查看离线版本)关于指针、引用与
const
的条目 - 其他 C++ 编码与风格指南(如 Google C++ Style Guide)