前言
C语言的多维数组和指针的语法十分复杂,一直以来为人诟病,是C语言使用者的噩梦。通常情况下我们会采取一些方式规避多维数组。但是“明知山有虎,偏向虎山行”,我将会深入探究多维数组和指针的关系。
(这块内容我看了很多资料,但是我认为相当一部分讲的都有问题,这篇文章主要谈我的理解,用词可能不规范,如果有问题,希望指正。)
多维数组数据类型
为了不失普遍性,我们以三维数组
int A[2][3][4];
为例探究一下各种转换的数据类型。
数组名类型和隐式转换
首先要明确的一点是:
数组不是指针,但是数组能转换成指针,或者说退化成指针
A的数据类型是int[2][3][4],是“顶天立地”的数组类型,并非指针。
然而,匪夷所思的是
A+1的数据类型是int(*)[3][4],退化成了指针。对此我的理解是:数组为了能与1进行+运算,发生了隐式类型转换,即A和1都“偷偷地”变成了指针(C语言特色)。而对于数组向指针的隐式转换就是降一维的指针。这也解释了
A[0]的数据类型是int[3][4],因为A[0]等价于*(A+0)。
数组名取*和&的数据类型
数组名是个很奇怪的存在,既能取*也能取&。
*A的数据类型是int[3][4],也就是说*A完全等价于*(A+0),即A[0]。
&A的数据类型是int(*)[2][3][4],就是指向数组的指针。
烧脑的来了:
&*A的数据类型是int(*)[3][4]。
*&A的数据类型是int[2][3][4],完全等价于A。
&和*互为逆运算,放在一起作用应该抵消,应该像*&A一样等价于A,那么&*A为什么不是A?就像之前所讲对数组名取*隐含了隐式类型转换,即&*(int(*)[3][4])A。
也就是说:
A的类型本身是int[2][3][4],但同时也匹配int(*)[3][4](当要对A解引用时)。
总结
表达式 | 数据类型 |
---|---|
A | int[2][3][4] |
A+0 | int(*)[3][4] |
A[0] | int[3][4] |
*A | int[3][4] |
&A | int(*)[2][3][4] |
&*A | int(*)[3][4] |
*&A | int[2][3][4] |
多维数组与函数
多维数组做函数参数
void test(int a[n][3][4]);
void test(int a[][3][4]);
void test(int (*a)[3][4]);
这3个都能匹配int[n][3][4]和int(*)[3][4],其中n是任意正整数。这也再次证明了当对数组名解引用时会变成低一维的指针,因此最高维的长度也变得不重要了。
返回多维数组指针
需要明确一点:
数组不能作函数返回值,只能返回数组指针
int (*test(void))[3][4];
这个函数的返回值匹配int (*)[3][4],表面上返回的是一个二维数组指针,但我们之前分析过三维数组名和二维数组指针的关系。
int (*a)[3][4] = test();
int tem = a[0][0][0];
我们可以看到二维数组指针也可以进行三次取下标操作,这个取下标的过程对于多重指针来说是不断“摘星”,对数组就是不停地由n维数组向(n-1)维数组隐式降维。
结论
综上,我们可以得出结论:
n维数组能够退化成n-1维数组指针
让我们回忆最开始学数组时,一维数组似乎是可以和指针混淆使用的。其实一维数组只是多维数组的一个特例,其中的道理是一维数组退化成零维数组指针,而零维数组指针不存在,只能是指针了。