【时时三省】(C语言基础)通过指针引用多维数组
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省
指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用方法上,多维数组的指针比一维数组的指针要复杂一些。
1.多维数组元素的地址
为了说清楚指向多维数组元素的指针,先想一下多维数组的性质,以二维数组为例设有一个二维数组a,它有3行4列。它的定义为
int a [ 3 ] [ 4 ] = {{ 1,3,5,7 },{ 9,11,13,15 },{ 17,19,21,23 }};
a是二维数组名。a数组包含3行,即3个行元素;a [ 0 ],a [ 1 ],a [ 2 ]。而每一个行元素又是一个一维数组,它包含4个元素(即4个列元素)。例如,a [ 0 ]所代表的一维数组又包含4个元素:a [ 0 ] [ 0 ],a[0][1],a[0][2],a[0][3],可以认为二维数组是“数组的数组”,即二维数组a是由3个一维数组所组成的。
从二维数组的角度来看,a代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素,而是由4个整型元素所组成的一维数组,因此a代表的是首行(即序号为0的行)的起始地址。a +1代表序号为1的行的起始地址。如果二维数组的首行的起始地址为2000,一个整型数据占4个字节,则a +1的值应该是2000 + 4×4 = 2016(因为第0行有4个整型数据)。a +1指向a[1],或者说,a +1的值是a[1]的起始地址。a + 2代表a[ 2 ]的起始地址,它的值是2032。
a [ 0 ],a [ 1 ],a [ 2 ]既然是一维数组名,已知,数组名代表数组首元素地址,因此a [ 0 ]代表一维数组a [ 0 ]中第0列元素的地址,即& a[0][0]。同理,a[ 1 ]的值是&a [ 1 ] [ 0 ], a [ 2 ]的值是&a[2][0]。
请考虑a数组0行1列元素的地址怎么表示? a [ 0 ]是一维数组名,该一维数组中序号为1的元素的地址显然应该用a [ 0 ] +1来表示。此时“a [ 0 ] +1”中的1代表1个列元素的字节数,即4个字节。a[0]的值是2000,a [ 0 ] +1的值是2004(而不是2016),这是因为现在是在一维数组范围内讨论问题的,正如有一个一维数组x,x+1是其第1个元素x [ 1 ]的地址一样。a [ 0 ] + 0,a [ 0 ] +1,a [ 0 ] + 2,a [ 0 ] + 3分别是a [ 0 ] [ 0 ],a [ 0 ] [ 1 ],a[0][2]a[0][ 3 ]元素的地址(即& a [ 0 ] [ 0 ],& [ 0 ] [ 1 ],&[ 0 ] [ 2 ],& [ 0 ] [ 3 ])。
前已述及,a [ 0 ]和* ( a + 0 )等价,a [ 1 ]和*( a +1 )等价,a [i]和*( a + i )等价。因此,a [ 0 ] +1和* ( a + 0 ) +1都是& a [ 0 ] [ 1 ]。a [ 1 ] + 2和* ( a +1 ) + 2的值都是&a [ 1 ] [ 2 ]。请注意不要将* ( a +1 ) + 2错写成* ( a +1 + 2 ),后者变成* ( a + 3 )了,相当于a [ 3 ]。
进一步分析,欲得到a [ 0 ] [ 1的值,用地址法怎么表示呢?既然a [ 0 ] +1和= ( a + 0 ) +1是[ 0 ] [ 1 ]的地址,那么,* ( a [ 0 ] +1 )就是a [ 0 ] [ 1 ]的值。同理,* ( * ( a + 0 ) +1 )或* ( * a +1 )也是a [ 0 ] [ 1 ]的值。* ( a [ i ] + j )或* ( * ( a + i ) + i )是a [i][j]的值。务请记住* ( a + i )和a [ i ]是等价的。
a [ i ]的性质作进一步说明。a [ i ]从形式上看是a数组中序号为i的元素。如果a是一维数组名,则a [ i ]代表a数组序号为i的元素的存储单元。a [ i ]是一个有确定地址的存储单元。但如果a是二维数组,则a[i]是一维数组名,它只是一个地址,并不代表一个存储单元,也不代表存储单元中的值(如同一维数组名只是一个指针常量一样)。a,a + i,a [ i ],* ( a + i ),* ( a + i ) + j,a[ i ]+ j都是地址。而* ( a [ i ] + j )和* ( * ( a + i ) + j )是二维数组元素a [ i ] [ j ]的值。
a +1和* ( a +1 )的值都是2016。有些人认为a +1是地址,* ( a +1 )是该地址指向的存储单元中的内容,怎么会是同一个值呢?的确,二维数组中有些概念比较复杂难懂,要仔细消化,反复思考。
首先说明,a +1是二维数组a中序号为1的行的起始地址(序号从0起算),而*(a +1)并不是a +1单元的内容(值),因为a +1并不是一个数组元素的地址,也就谈不上存储单元的内容了。* ( a +1 )就是a [ 1 ],而a [ 1 ]是一维数组名,所以也是地址,它指向a [ 1 ] [ 0 ]。a[ 1 ]和* (a +1 )都是二维数组元素a [ 1 ] [ 0 ]的地址的不同的表示形式。
说明:C语言的地址信息中既包含位置信息(如内存编号2000),这包含它所指向的数据的类型信息。现在a [ 0 ]是一维数组名,它是一维数组中起始元素的地址,a是二维数组名,它是二维数组的首行起始地址,二者的纯地址是相同的;即2000,但它们的基类型不同,即它们指向的数据的类型不同,前者是整型数据,后者是一维数组。如果用一个指针变量pt来指向此一维数组,应当这样定义:int ( * pt ) [ 4 ] ;表示pt指向由4个整型元素组成的一维数组,此时指针变量pt的基类型是由4个整型元素组成的一维数组。
再次强调:二维数组名(如a)是指向行(一维数组)的。因此a +1中的“1”代表一行中全部元素所占的字节数。一维数组名(如a [ 0 ],a [ 1 ])是指向列元素的。a [ 0 ] +1中的1代表一个a元素所占的字节数。在指向行的指针前面加一个*,就转换为指向列的指针。例如,a和a +1是指向行的指针,在它们前面加一个*就是* a和* ( a +1 ),它们就成为指向列的指针,分别指向a数组0行0列的元素和1行0列的元素。反之,在指向列的指针前面加&,就成为指向行的指针。例如a [ 0 ]是指向0行0列元素的指针,在它前面加一个8,得& a[0],由于a [ 0 ]与* ( a + 0 )等价,因此& a [ 0 ]与& * a等价,也就是与a等价,它指向二维数组的0行。
注意:不要把& a [ i ]简单地理解为a [i]元素的存储单元的地址,因为并不存在a [ i ]这样一个实际的数据存储单元。它只是一种地址的计算方法,能得到第i行的起始地址,& a [ i ]和a[ i ]的值是一样的,但它们的基类型是不同的。& a[ i ]或a + i指向行,而a [ i ]或* ( a + i )指向列。当列下标j为0时,& a[i]和a[i](即a[i]+j)值相等,即它们的纯地址相同,但应注意它们所指向的对象的类型是不同的,即指针的基类型是不同的。* ( a + i )只是[ i ]的另一种表示形式,不要简单地认为* ( a + i )是“a + i所指单元中的内容”。在一维数组中a + i所指的是一个数组元素的存储单元,在该单元中有具体值,上述说法是正确的。而对二维数组,a + i不是指向具体存储单元而是指向行(即指向一维数组)。在二维数组中,a + i、a [ i ],* ( a + i ),& a [ i ],&a[i][0]的值相等,即它们都代表同一地址,但基类型不同。