问题
今天群里有人发了个问题,问题如下:
第一个赋值可以,为什么第二个不行
int * x = NULL;
int const * y = NULL;
y = x;
int ** z = 0;
int const ** n = 0;
n = z
当时我懵了一下,随即想到可能是int const **的问题,便把代码改成:
int ** z = 0;
int * const * n = 0;
n = z;
编译通过,以为解决了问题,想也没想,便发回给提问者。
殊不知,提问者问了好几个问题后,我又懵了,-_-!
这个const是属于那一边的?
可以是
int (* const) * n = 0;
也可以是
int * (const *) n = 0;
那么n就是一个指向(int *)的const int 指针
后来提问者发了书里面的一句话:
如果const和(或)volatile关键字后面紧跟类型说明符(如int, long等),它作用于类型说明符,在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
本人觉得这句话不够确切,一般情况下,我们会把代码写成:
const int * p;
这和书中描述的一致,const 此时作用于int。但我们也有另外一种写法
int const * p;
虽然大家都知道这个const 必定作用于int的,但按照那句话的方法,const找不到作用的地方了。
好吧,这有点钻牛角了。既然是从书中出来的,还是得用代码来证明一下。
试验
回归到顶部问题,其实就是类型匹配问题,如果类型不匹配,那么编译就会报错,所以通过编译器(VS2010)来作为我们的裁判,const到底是作用于谁。
先铺好两个必须的变量:
int * p_to_v_1 = new int(1);;
int * p_to_v_2 = new int(2);
第一试验
int * * ppTest1 = &p_to_v_1;
ppTest1 = &p_to_v_2;
*ppTest1 = p_to_v_2;
**ppTest1 = 10;
这个应该理解上没有多大的问题,所以不解释。
编译通过,没报任何错误,结论:
*ppTest1也是int * 类型。
**ppTest1是int类型。
第二试验
int const * * ppTest2 = &p_to_v_1;
ppTest2 = &p_to_v_2;
*ppTest2 = p_to_v_2;
**ppTest2 = 10;
这个比较简单,ppTest2指向的是int const *的指针,所以第三个**ppTest2的赋值编译器报错:表达式必须是可修改的左值。
*ppTest2也是int const * (或const int *)类型。
**ppTest2是const int 类型,不可改变,所以赋值时出错。
第三试验
int * const * ppTest3 = &p_to_v_1;
ppTest3 = &p_to_v_2;
*ppTest3 = p_to_v_2;
**ppTest3 = 10;
const 来到两个星号中间,那么就是上面我遇到的问题了。
编译器此时报错是 *ppTest3 = p_to_v2; 这行代码,错误是:表达式必须是可修改的左值。
p_to_v2是指针变量,那么*ppTest3就是指针常量
*ppTest3是int * const类型。(印证了书中那句话)
**ppTest3那一行没报错,那么**ppTest3就是int类型
第四试验
int * * const ppTest4 = &p_to_v_1;
ppTest4 = &p_to_v_2;
*ppTest4 = p_to_v_2;
**ppTest4 = 10;
报错的是“ppTest4 = p_to_v2;”这行,错误信息和上面例子一样,那么ppTest4就是一个指向指针的指针常量,ppTest4是不可更改的。
第五试验
int const * const * ppTest5 = &p_to_v_1;
ppTest5 = &p_to_v_2;
*ppTest5 = p_to_v_2;
**ppTest5 = 10;
有了前面的基础,应该很容易判断这里报错的是哪个。
没错,第3行和第4行错误,*ppTest5是指针常量,**ppTest5是常量。
第六试验
int * const const * ppTest6 = &p_to_v_1;
ppTest6 = &p_to_v_2;
*ppTest6 = p_to_v_2;
**ppTest6 = 10;
这个定义看起来有点奇怪,虽然会有编译警告,但是可以通过的。
报错的只有第3行(*ppTest6 = p_to_v_2;),因为*ppTest6是指针常量。
为什么第2行不报错呢?ppTest6是指向指针常量的指针,&p_to_v_2的结果又是指向指针变量的指针。
因为这个赋值的道理就如const int * 类型可以被int *类型赋值一样(如函数void f(const int * cp)中的参数,f被调用时,传入的参数可以是const int*,也可以为int *),一个是指向int常量的指针,一个是指向int变量的指针。
这里的定义(int (* const) (const *) ppTest6 )还有个地方需要注意一下,刚才说了,ppTest6是指向指针常量的指针;而刚好,*ppTest6又是指针常量,那这个定义就相当于
int * const * ppTest6
第七试验
int * const * const ppTest7 = &p_to_v_1;
ppTest7 = &p_to_v_2;
*ppTest7 = p_to_v_2;
**ppTest7 = 10;
2和3行报错,ppTest7是常量,*ppTest7也是。
第八试验
int const * const * const ppTest8 = &p_to_v_1;
ppTest8 = &p_to_v_2;
*ppTest8 = p_to_v_2;
**ppTest8 = 10;
这个2、3、4都报错,应该不用怎么解释了吧。。。
总结一下,刚才那句话大部分还是对的,就差了我刚才提到的那一点,嘿嘿。
如果const和(或)volatile关键字后面紧跟类型说明符(如int, long等),它作用于类型说明符,在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
解决问题
int * x = NULL;
int const * y = NULL;
y = x;
int ** z = 0;
int const ** n = 0;
n = z
现在来看这些指针,应该比较明确:
z就是指向int *的指针;
而n就是指向const int *的指针;
n和z最终指向类型(一个是int, 一个是const int)是不一样的,所以赋不了值。
就如以下代码
int * x = NULL;
int * const y = NULL;
y = x;
请不要跟第六试验混淆,第六个试验中,ppTest6和p_to_v_2最终指向的都是int类型。
在一般情况下,应该不会出现这种const和指针的指针混用的X疼情况,不过这样一来,自己也是对指针有更深一层了解,指针确实是C和C++中最难攻克的一关,希望通过这能帮助到其他人,如果有不足,请不吝赐教。