const 限定符
const 限定符
使用 const 可以定义一类值无法被改变的变量。const 对象创建后无法被改变,所以必须要进行初始化。
const int bufsize = 512;
bufsize = 512; // error: 无法改变 const 对象的值
初始化与 const
初始值可以是任意的表达式,同时可以使用 const 对象对非 const 对象进行赋值。
int i = 42;
const int ci = i;
int j = ci;
int j = ci
使用了 ci 的值,而并没有改变 ci 的值,而且在之后 j 与 ci 就没有关系了,对 j 的任意改变都是允许的。
const 的引用
对常量的引用(reference to const):不可修改它绑定的对象。
const int ci = 1024;
const int &r1 = ci;
r1 = 42; // error: 不可通过 r1 改变 ci
int &r2 = ci; // error: 非常量引用不可绑定一个常量对象
- 常量引用是 reference to const 的简称。引用本身不是对象,所以引用改变与否没有意义;同时我们无法通过这个引用所引用的对象,所以从“值无法改变”这个角度上来说,引用本身就是常量。
这句话清楚地解释了两者的关系:
引用的对象是常量与否决定其所能参与的操作,却无论如何都不会影响的引用和对象的绑定关系本身。
初始化和对 const 的引用
引用的类型必须与其所引用的对象一致。但还有两种例外:
- 初始化常量引用时允许使用任意表达式作为初始值,只要这个表达式可以转换成引用的类型即可;
- 常量引用仅仅对引用可参与的操作进行限定,对引用所绑定的对象本身是否是常量没有限制。
针对第一点,我们要清楚在一个常量引用绑定到另外一种类型上时发生了什么,看这段代码:
#include <iostream>
int main(void)
{
int i = 42;
const int& r1 = i;
const int& r2 = 42;
const int& r3 = r1 * 2;
std::cout << r1 << ' ' << r2 << ' ' << r3 << std::endl;
double dval = 3.14;
const int& r4 = dval;
std::cout << r4 << std::endl;
dval = 0;
std::cout << r4 << std::endl;
return 0;
}
这段代码可以通过编译,但是可能和我们的预期不同,在 dval 变化前后都是 3。问题在于这里 r4 绑定了一个临时量( temporary ):
const int temp1 = r1 * 2;
const int &r3 = temp1;
const int temp = dval;
const int &r4 = temp;
当 r4 是常量引用时,但如果不是常量引用,就会出现问题。这时通过 r4 修改的对象实际上是绑定的临时量,而并不是初始化时使用的对象,这样的修改是没有意义的,所以 C++ 将这种行为归为非法。这也和 Effective C++ 中尽量使用 const 的观点契合。
针对第二点,对常量引用做出了更详细的解读:常量引用可以绑定一个常量对象,也完全可以绑定一个非常量对象,这两种情况下,都不可以通过常量引用修改所绑定对象的值,也就是之前强调的“不会影响的引用和对象的绑定关系本身”。
常量引用既可以绑定常量对象,也可以绑定非常量对象;
非常量引用只能绑定非常量对象,不可以绑定常量对象。
指针与 const
- pointer to const
存放常量对象的地址,与常量引用类似,也可以存放非常量对象的地址,但是不能通过其改变对象的值。 - const 指针
指针本身的值不改变。强调只可以指向初始化时使用的对象,可以通过这个指针修改指向对象的值。
const 在 * 左边表示是一个 pointer to const,在 * 右边表示是一个 const 指针。当然也可以定义一个指向常量的 const 指针:
int i = 0;
const int *pi = &i; // pointer to const
int *const pi1 = &i; // const pointer
const int *const pi2 = &i; // const pointer to const
顶层 const 和底层 const
顶层 const:表示对象本身是个常量;
底层 const:与指针和引用等复合类型部分相关,表示指针指向、引用绑定的对象是常量。
constexpr 和常量表达式
常量表达式指值不会改变且在编译过程中就能得到计算结果的表达式。
然而分辨一个初始值到底是不是常量表达式是几乎不可能完成的任务,所以使用 constexpr 类型以便由编译器验证变量的值是否是一个常量表达式,声明为 constexpr 的变量一定是个常量,必须用常量表达式初始化,或者 constexpr 函数。
指针与 constexpr
一个 constexpr 指针的初始值必须为 nullptr 或 0,或者是存储于某个固定地址中的对象。
constexpr 指针相当于 const pointer,是一个顶层 const。
函数体外定义的对象以及局部 static 对象存储在固定地址中,可以用这些地址初始化 constexpr 指针。