对于指针来说,指针本身是一个对象,而且它又可以指向另外一个对象,因此就涉及到指针本身是不是常量以及指针所指的对象是不是常量这两个问题。这两个问题是相互独立的。我们用顶层const 表示指针本身是一个常量,用底层const 表示指针所指的对象是一个常量。
一般来说,顶层const 可以表示任意的对象是常量,对任何数据类型都适用,如算数类型、类类型、指针类型等。比如看下面的几个声明:
const int ci = 42; // 不能改变ci的值,这是一个顶层const
int i = 0;
int *const p1 = &i; // 不能改变p1的值,这是一个顶层const
底层const与指针和引用等复合类型的基本类型部分有关。指针类型既可以是顶层const也可以是底层const,而引用类型只能是底层const。事实上,底层const就是指针所指的对象是常量或引用所绑定的对象是常量。在上面的几个声明的基础上,比如:
const int *p2 = &ci; // 可以改变p2的值,但p2所指对象的值不能改变,这是一个底层const
const int *const p3 = p2; // 第一个const是底层const,第二个const是顶层const
const int &r = ci; // 用于声明引用的const都是底层const
执行对象的拷贝操作时,常量是顶层const还是底层const有明显的区别。其中,顶层const不受影响,比如:
i = ci; // 正确:ci是顶层const,对拷贝操作无影响
p2 = p3; // 正确:p3的顶层const对拷贝操作无影响
但是指向对象拷贝操作时,底层const是有所限制的。拷入和拷出的对象必须具有相同的底层const资格,或两个对象的数据类型必须能够转换(非常量可以转换成常量,而常量不能转换成非常量)。比如:
int *p = p3; // 错误:p3是底层const,而p不是
p2 = p3; // 正确:p2和p3都是底层const
p2 = &i; // 正确:int*可以转换成const int*
int &r = ci; // 错误:普通的int&不能绑定到int常量上
const int &r2 = i; // 正确:const int&可以绑定到一个普通int上