21-指针的兼容性
10.7.2 指针的兼容性
指针之间的赋值比数值类型之间的赋值要严格。例如,不用类型转换就可以把 int 类型的值赋给 double 类型的变量,但是两个类型的指针不能这样做。
int n = 5;
double x;
int * p1 = &n;
double * pd = &x;
x = n; // 隐式类型转换
pd = p1; // 编译时错误
更复杂的类型也是如此。假设有如下声明:
int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2; // 一个指向指针的指针
有如下的语句:
pt = &ar1[0][0]; // 都是指向int的指针
pt = ar1[0]; // 都是指向int的指针
pt = ar1; // 无效
pa = ar1; // 都是指向内含3个int类型元素数组的指针
pa = ar2; // 无效
p2 = &pt; // 都是指向int *的指针
*p2 = ar2[0]; // 都是指向int的指针
p2 = ar2; // 无效
注意,以上无效的赋值表达式语句中涉及的两个指针都是指向不同的类型。例如, pt 指向一个 int 类型值,而 ar1 指向一个内含3个 int 类型元素的数组。类似地, pa 指向一个内含3个 int 类型元素的数组,所以它与 ar1 的类型兼容,但是 ar2 指向一个内含2个 int 类型元素的数组,所以 pa 与 ar2 不兼容。
上面的最后两个例子有些棘手。变量 p2 是指向指针的指针,它指向的指针指向 int ,而 ar2 是指向数组的指针,该数组内含2个 int 类型的元素。所以, p2 和 ar2 的类型不同,不能把 ar2 赋给 p2 。但是,* p2 是指向 int 的指针,与 ar2[0] 兼容。因为 ar2[0] 是指向该数组首元素( ar2[0][0] )的指针,所以 ar2[0] 也是指向 int 的指针。
一般而言,多重解引用让人费解。例如,考虑下面的代码:
int x = 20;
const int y = 23;
int * p1 = &x;
const int * p2 = &y;
const int ** pp2;
p1 = p2; // 不安全 -- 把const指针赋给非const指针
p2 = p1; // 有效 -- 把非const指针赋给const指针
pp2 = &p1; // 不安全 –- 嵌套指针类型赋值
前面提到过,把 const 指针赋给非 const 指针不安全,因为这样可以使用新的指针改变 const 指针指向的数据。编译器在编译代码时,可能会给出警告,执行这样的代码是未定义的。但是把非 const 指针赋给 const 指针没问题,前提是只进行一级解引用:
p2 = p1; // 有效 -- 把非const指针赋给const指针
但是进行两级解引用时,这样的赋值也不安全,例如,考虑下面的代码:
const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1; // 允许,但是这导致const限定符失效(根据第1行代码,不能通过**pp2修改它所指向的内容)
*pp2 = &n; // 有效,两者都声明为const,但是这将导致p1指向n(*pp2已被修改)
*p1 = 10; //有效,但是这将改变n的值(但是根据第3行代码,不能修改n的值)
发生了什么?如前所示,标准规定了通过非 const 指针更改 const 数据是未定义的。例如,在Terminal中(OS X对底层UNIX系统的访问)使用gcc编译包含以上代码的小程序,导致 n 最终的值是 13 ,但是在相同系统下使用clang来编译, n 最终的值是 10 。两个编译器都给出指针类型不兼容的警告。当然,可以忽略这些警告,但是最好不要相信该程序运行的结果,这些结果都是未定义的。
C const和C++ const
C和C++中 const 的用法很相似,但是并不完全相同。区别之一是,C++允许在声明数组大小时使用 const 整数,而C却不允许。区别之二是,C++的指针赋值检查更严格:
const int y;
const int * p2 = &y;
int * p1;
p1 = p2; // C++中不允许这样做,但是C可能只给出警告
C++不允许把 const 指针赋给非 const 指针。而C则允许这样做,但是如果通过 p1 更改 y ,其行为是未定义的。