首先,const 是一个限定符,被它修饰的变量的值不能改变。对于一般的变量来说,其实没有顶层 const 和底层 const 的区别,而只有向指针这类复合类型的基本变量,才有这样的区别。
参考常量指针与指向常量的指针,指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
用名词 顶层const(top-level const) 表示指针本身是个常量,而用名词 底层const(low-level const) 表示指针所指的对象是一个常量。
(1)更一般地来说,顶层const 可以表示任意的对象是常量,这一点对任何数据类型都是用,如算数类型、类、指针等;底层const 则与指针和引用等复合类型的基本类型部分有关。
int v1 = 0;
int *const p1 = &v1; // 不能改变 p1 的值,这是一个顶层 const
const int v2 = 0; // 不能改变 v2 的值,这是一个顶层 const
const int *p2 = &v2; // 允许改变 p2 的值,这是一个底层 const
const int *const p3 = p2; // 靠右的const是顶层 const,靠左的是底层 const
const int &r = v2; // 用于声明引用的 const 都是底层 const
因为引用本身不是一个对象,仅仅是其他对象的别名,所以不存在“常量引用(引用自身是个常量)”。因此用于引用声明的 const 都是底层 const,表示其指向的对象是一个常量对象。
(2)当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层 const 资格。
底层 const 表示不可以通过该底层 const 去修改其所指向对象的值。
在上面的例子中,p3
既是一个顶层 const,又是一个底层 const。
看下面的例子,拷贝 p3
时不在乎其是不是一个顶层 const,但是必须清楚它是不是一个底层 const,也就是必须清楚它指向的对象得是一个常量:
- 因此不能用
p3
去初始化p
,因为p
指向的是一个普通的(非常量)整数; - 另一方面,
p3
的值可以赋给p2
,是因为这两个指针都是底层 const,尽管p3
同时也是一个常量指针(顶层 const),仅就这次赋值而言不会有什么影响。
int *p = p3; // 错误:p3 包含底层 const 的定义,而 p 没有
p2 = p3; // 正确:p2 和 p3 都是底层 const,而且可以将一个常量对象赋值给非常量
p2 = &v1; // 正确:int * 能转换成 const int *
int &r1 = v2; // 错误:普通的 int& 不能绑定到 int 常量上
const int &r2 = i; // 正确:const int& 可以绑定到 int 常量上