《C++primer》中写到:顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。
顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用。底层const则与指针和引用等复合类型的基本类型部分有关,比较特殊的是,指针类型既可以是顶层const也可以是底层const
cpp
int i = 0;
int *const p1 = &i; // 顶层const
const int ci = 42; // 顶层const
const int *p2 = &ci; // 底层const
const int *const p3 = p2; // 靠右的const是顶层const,靠左的const是底层const
const int &r = ci; // 用于声明引用的const都是底层const
感觉还是好难理解,那么,如何分辨顶层const和底层const呢?
顶层const是施加在类型本身上的const。
cpp
const int a; // const施加在类型本身int上,所以是顶层const
const int *p; // const施加在int上,而没有施加在类型本身int *上,所以不是顶层const
int *const p; // const施加在类型本身int *上,所以是顶层const
const int &ref; // const施加在int上,而没有施加在类型本身int &上(当然也不可能),所以不是顶层const
const int arr[5]; // const施加在数组的元素类型int上,而没有施加在数组类型int[5]本身,所以不是顶层const
// 这里补充一句,如果对数组类型使用cv修饰符,则cv修饰符修饰的是数组的元素类型(C++20标准9.3.4.5-5)
"以*****分界,把一个声明从右向左 读"
注意语法,* 读作 pointer to (指向...的指针),const (常量) 是形容词,char (变量类型) 和 p (变量名) ,只要这样读,哪怕是初中生也分得清了
- const char * p 读作:p is a pointer to a const char,译:p 是一个指针(变量),它指向一个常量字符(const char)。
- char * const p 读作:p is a const pointer to a char,译:p 是一个常量指针(const p),它指向一个字符(变量)。
cpp
const int v2 = 0; // 顶层const
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
// 分解一下
const int *p2 = &v2; // p2指向的内容不可变,顶层const
const int * const p3 = &i; // p3指向的内容和p3本身都不可变,既是顶层const也是底层const
const int &r2 = v2; // r2是一个常引用,它的值不可变,是一个顶层const
// int v1 = v2;
// int *p1 = &v1;
// const int *p2 = &v2;
p1 = p2; // 非法
// p2是一个底层const,它指向的内容不可变,强约束
// p1是个普通整型指针,属松散约束
// 从强约束到宽约束的转换是不允许的
// 编译器报错
// error C2440: '=': cannot convert from 'const int *' to 'int *'
p1 = p3; // 用p1接收p2都已经非法了,何况p3既是底层const也是顶层const
p2 = p1; // 合法,p2是底层const,指针p2本身是可以变化的
p2 = p3; // 合法
// p3是双const约束,内容和指针本身都不可以变
// p2是底层const,它控制的是指向的内容的只读性
// 用p2接收p3, 各司其职,非常丝滑
为什么要区分顶层const和底层const呢,有两个作用:
- 执行对象拷贝时有限制:常量的底层const不能赋值给非常量的底层const。
也就是说,区分顶层const和底层const可以避免赋值错误
cpp
int num_c = 3;
const int *p_c = &num_c; // p_c为底层const的指针
// int *p_d = p_c; // 错误,不能将底层const指针赋值给非顶层const指针
const int *p_d = p_c; // 正确,可以将底层const指针复制给底层const指针
- 使用命名的强制类型转换函数const_cast时,需要能够分辨底层const和顶层const,因为const_cast只能改变运算对象的底层cosnt
cpp
int num_e = 4;
const int *p_e = &num_e;
// *p_e = 5; // 错误,不能改变底层const指针指向的内容
int *p_f = const_cast<int *>(p_e); // 正确,const_cast可以改变运算对象的底层const。但是使用时一定要知道num_e不是const的类型。
*p_f = 5; // 正确,非顶层const指针可以改变指向的内容
cout << num_e; // 输出5