顶层const
指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
用名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是一个常量。
比较特殊的是,指针类型即可以使顶层const也可以是底层const:
int i = 0;
int *const p1 = i; //不能改变p1的值,这是一个顶层const
const int ci = 1024; //不能改变ci的值,这是一个顶层const
const int *p2 = &ci; //允许改变p2的值,这是一个底层const
const int *const p3 = p2; //靠左的是一个底层const,靠右的是一个顶层const
const int &r = ci; //用于声明引用的const都是底层const
当执行对象的拷贝操作是,顶层const不受什么影响:
i = ci; //正确:ci是一个顶层const
P2 = p3; //正确: p2和p3指向的对象类型相同,p3是顶层
当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层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上
constexpr和常量表达式
便于编译器验证变量的值是否是一个常量表达式;声明为constexpr的变量一定是一个常量而且必须用常量表达式初始化:
constexpr int mf = 1; //1是一个常量表达式
constexpr int limit = mf + 1; //mf +1 是一个常量表达式
constexpr int sz = size(); //只有当size是一个constexpr函数时,才是正确的声明
字面值类型
算数类型、引用和指针都属于字面值类型。但他们的初始值受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是储存于某个固定地址中的对象。
指针和constexpr
必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p是一个指向整形常量的指针
constexpr int *q = nullptr; //q是一个指向整型的常量指针
p和q的类型相差甚远,p是一个指向整型常量的指针,而q是一个常量指针,是顶层const
与其他常量指针类似,constexpr指针既可以指向常量也可以指向一个非常量:
constexpr int *np = nullptr;
int j = 0;
constexpr int i = 1024; //i的类型是整型常量
constexpr const int *p = &i; //p是指向整型常量i的常量指针
constexpr int *p1 = &j; //p1是指向整型的常量指针