在学习到C++的“引用”之前,我是支持在纯C中使用
int *p;
这种写法的。相比直接将int*
理解为指针(虽然更human friendly一些),上面的“运算式”写法避免了对*
符号理解的二义性,即可以永远将*
理解为“按址取值”的一个运算符,“p
类型为指针”的结论由编译器根据“*p
为int
类型”这个声明反推出来。更重要的是这个理解方式可以应用到更加复杂的类型声明语句中,比如典型的
char *argv[];
要声明的变量名argv
按照既定的运算顺序(一元运算符优先级最高,且遵循从右向左的结合顺序),先执行数组索引运算,再执行按址取值运算,最终得到char
类型,那么就可以反推出
argv[i]
为指向char
的指针,从而argv
为一个由char
指针组成的数组。当然这个例子即使用char* argv[]
理解也是OK的,还不能体现前者的优势,那么像下面这样带括号的呢?
int (*p)[N];
p
在这里先执行按址取值运算,再执行数组索引运算,最终得到int
类型,从而反推出
*p
为一个长度为N的int
数组,从而p
为指向这个数组的一个指针。
这样子理解虽然对编译器比较友好而反人类,但确实是精准无歧义的。
再比如二维数组,
int a[M][N];
int (*p)[N] = a;
凭什么下面的p
能够无需类型转换地指向上面的a
?仔细研究下就能体会到这种贴近编译器的理解方式的强大之处。甚至包括函数指针都可以按上述方式理解。
========分割线========
顺嘴可以说下强制类型转换,把声明表达式中的变量名和分号去掉,两侧套上括号不就结了?反正变量名的位置可以根据运算符的前后缀特性推出来,并不影响编译器的类型推导。比如
p2 = (int (*)[N])some_pointer;
虽然int (*)[N]
这个类型转换符看起来挺复杂,但是只需考虑到*
是前缀运算符,[]
是后缀运算符,隐藏的变量名位置就显而易见了(没错正是int (*p)[N]
,这个填充是唯一的)。
========又一分割线========
int *p = &a;
该作何解释的问题。这个问题其实只要换成一个像上面一样复杂的声明就好理解了
int a[M][N];
some_type line = a[0]; /* a的第一行,一个长度N的数组 */
int (*p)[N] = &line; /* p作为一个指向数组的指针的声明初始化 */
你会觉得这个初始化语句等价于
int (*p)[N];
p = &line;
还是
int (*p)[N];
(*p)[i] = &line;
呢?
即使不好分析p
是什么,(*p)[i]
是一个int
是显然的结论,由此可看出无论p的声明表达式有多复杂,初始化语句的等号左边永远都会是一个由保留关键字int/char/long/float/double
定义的基本数据类型,如果后者成立,一切派生类型的初始化就都完犊子了。所以合理的理解方式只能是前者。