喵哥在牛客网遇到这么一题选择题:
class A { };
void f(const A** p) {}
void g(const A * const *p) {}
void k(const A*&p) {}
int main()
{
const A * ca = new A();
A*a = new A();
A** p = &a;
k(ca); //1
f(p); //2
g(p);//3
return 0;
}
大概要选的选项是1,2,3语句正确与否的组合,正确答案是1,3正确,2错误。
这是一道关于const对象赋值的判断,扯远一点:
const包含顶层const和底层const。顶层const表示对象本身是常量,而底层const表示对象指向的对象是一个常量。通常顶层const对任何数据类型都适用,但是底层const只包含指针和引用,其中引用const必定是底层const。
顶层const的复制比较宽松,而底层const必须注意一些要点:在执行拷贝时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型可以互相转换。一般的,非常量对象可以转为常量(针对底层const)。
回到题中。
语句1中用到的k函数的参数是const A*&p,这是一个指向A类型常量的指针的引用,引用在此处的作用是可以传值。ca的类型正好是指向A类型常量的指针,正确。
语句2中用到的f函数的参数是const A** p,是一个指向A类型常量的二级指针(指向指向A类型常量的指针的指针)。p的类型是指向A类型的二级指针。根据前文所述:“一般的,非常量对象可以转为常量”,这样的赋值似乎是合理的,但是这里面存在一个毁灭性的缺陷,如下:
int main()
{
const int x = 1;
int* p;
const int** q = &p; //假设int**可以赋值到const int **
*q = &x; //现在将q指向的指针赋为常量x的地址(*q即为指针p的地址,这时p指向x)
*p = 2; //由于p是非常量的,所以对其赋值很正常,但是这样就修改了常量x的值!!!
printf("%d", x);
return 0;
}
可见,如果int**可以赋值到const int**会导致某些常量被“合情合理”地修改了,这跟常量的定义相违背。那么如何才能使得二级指针的非常量转换到常量呢?分析代码,发现只要*q = &x不成立即可,那么保证*q为常量。
const int*const* q = &p;
如此书写便可以保证不出错。事实上,这是C语言中遗留的缺陷,C++将其修复:禁止int**复制到const int** ,但是允许int**复制到const int*const*。从而语句3正确。