指针与多维数组的关系

 最近在复习C语言,学到C指针的时候,发现指针与多维数组间存在着一些很有意思的问题,这也是在我们使用指针时容易出现问题的地方。先从一个例子开始:

int zippo[4][2];

 这个例子声明了一个4x2共8个int类型的二维数组。数组名zippo是该数组首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素的地址(即&zippo[0][0]的值)相同。简而言之,zippo[0]是占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和&zippo[0]的值相同。

 给指针或者是地址加1,其值会增加相同类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占两个int大小,而zippo[0]指向的对象占用一个int大小。因此,zippo+1和zippo[0]+1的值不同。

 解引用一个指针(在指针前使用*运算符)或在数组名后使用带下标的[]运算符,得到引用对象代表的值。因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示存储在zippo[0][0]上的值。与此类似,*zippo代表数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址。该值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。**zippo和*&zippo[0][0]等价,相当于zippo[0][0],即一个int类型的值。总之,zippo是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针叫做双重间接(double indirection)。

 由于指针可以说是一个地址,在多维数组中,可以使用指针表示法和数组表示法表达同一个意思。一些常见的等价用法如下:

数组表示法 指针表示法 描述
zippo 二维数组首元素的地址,每个元素都是内含两个int类型元素的数组
zippo+2 二维数组第三个元素的地址,指向一维数组
&zippo[0][0] *zippo 二维数组第一个元素(一维数组)的首元素(int)的地址
zippo[0] 二维数组首元素的地址,是内含两个int类型元素的一维数组
&zippo[0] 二维数组首元素的地址,每个元素都是内含两个int类型元素的数组
*zippo[0] 二维数组首元素(一维数组)的的首元素的值,即zippo[0][0]
&zippo[2][0] *(zippo+2) 二维数组第三个元素(一维数组)的首元素(int)的地址
&zippo[2][1] *(zippo+2)+1 二维数组第三个元素(一维数组)的第二个元素(int)的地址
zippo[2][1] *(*(zippo+2)+1) 二维数组第三个元素(一维数组)的第二个元素(int)的值

一些复杂的声明

 了解了指针与多维数组的关系,下面再来了解一些复杂的数组声明熟悉一下。

声明 描述
int board[8][8]; 声明一个8x8的二维int数组
int **ptr; 声明一个指向指针的指针,被指向的指针指向int
int *risks[10]; 声明一个内含10个元素的数组,每个元素都是一个指向int的指针
int (*risks)[10]; 声明一个指向数组的指针,该数组内含10个int类型的值
int *oof[3][4]; 声明一个3x4的二维数组,每个元素都是指向int的指针
int (*off)[3][4]; 声明一个指向3x4的二维数组的指针,数组内含int类型值
int (* uof[3])[4]; 声明一个内含3个指针的数组,其中每个指针都指向一个内含4个int类型元素的数组

 要看懂如上的声明,关键要理解*,()和[]的优先级。有如下的几条规则。

 1.数组名后面的[]和函数名后的()具有相同的优先级。他们比*(解引用运算符)的优先级高。因此下面的声明是一个指针数组,而不是一个指向数组的指针: int *risks[10];

 2.[]和()的优先级相同,由于都是从左往右结合,所以在下面的声明中,在应用方括号之前,*先与rusks结合。因此rusks是一个指向数组的指针: int (* rusks)[10]。

 3.[]和()都是从左往右结合。因此下面声明的goods是一个由12个内含50个int类型值的数组组成的二维数组,不是一个由50个内含12个int类型值的数组组成的二维数组: int goods[12][50]。