指针和二维数组的关系
目录
一、二维数组的行地址和列地址
二、通过二维数组的行指针和列指针来引用二维数组元素
一、二维数组的行地址和列地址
在C语言中,可将一个二维数组看成是由若干个一维数组构成的。例如若有下面的定义:
int a[3][4];
则,其二维数组的逻辑存储结构如下图所示:
可通过下图来理解二维数组的行地址和列地址的概念。首先可将二维数组a看成是由a[0]、a[1]、a[2]三个元素组成的一维数组,a是它的数组名,代表其第一个元素a[0]的地址(&a[0])。根据一维数组与指针的关系可知,a+1表示的是首地址所指元素后面的第1个元素地址,即元素a[1]的地址(&a[1])。同理,a+2表示元素a[2]的地址(&a[2])。于时,通过这些地址就可以引用个元素的值了,例如*(a+0)或*a即元素a[0],*(a+1)即为元素a[1],*(a+2)即为元素a[2]。注意:这里所谓的元素事实上仍然是个地址,并非具体的数值。
其次,可将a[0]、a[1]和a[2]三个元素分别看成是由4个整型元素组成的一维数组的数组名。例如,a[0]可以看出是由元素a[0][0]、a[0][1]、a[0][2]和a[0][3]这4个整型元素组成的一维数组的数组名,代表该一维数组的第一个元素a[0][0]的地址(&a[0][0]),a[0]+1则代表元素a[0][1]的地址(&a[0][1])。因此,*(a[0]+0)即为元素a[0][0],*(a[0][1])即为元素a[0][1]。
注意:由于a[0]可以看出是由4个整形元素组成的一位数组的数组名。因此,a[0]+1中的数字1代表的是一个整型元素所占的存储单元的字节数,即二维数组的一列所占的字节数:1×sizeof(int);而a可看成由a[0]、a[1]、a[2]三个元素组成的一维数组的数组名,因此表达式a+1中的数字1代表的是一个含有4个整型元素的一维数组所占的存储单元的字节数,即二维数组的一行所占的字节数:4×sizeof(int)。
根据上面分析可归纳如下:a[i]即*(a+i)可以看出是一维数组a的下标为i的元素,同时,a[i]即*(a+i)又可看成是由a[i][0]、a[i][1]、a[i][2]和a[i][3]等4个元素组成的一维整型数组的数组名,代表这个一维数组第1个元素a[i][0]的地址(&a[i][0]);而a[i]+j即*(a+i)+j代表这个数组中下标为j的元素的地址,即&a[i][j]。*(a[i]+j)即*(*(a+i)+j)就代表这个地址所指向的元素的值,即a[i][j]。因此,下面4种表示a[i][j]的形式是等价的。
a[i][j]-------*(a[i]+j)---------*(*(a+i)+j)----------(*(a+i))[j]
如果将二维数组的数组名a看成一个行地址(第0行的地址),则a+i代表二维数组a的第i行的地址,a[i]可看成一个列地址,即第i行第0列的地址。行地址a每次加1,表示指向下一行,而列地址a[i]每次加1,表示指向下一列。
打个比方,二维数组的行地址好比一个宾馆房间所在的楼层号,二维数组的列地址好比一个宾馆房间所在的房间号,要想进入第i层的第j个房间,必须先从第1层开始登楼梯,登到第i层后,在从第i层的第1个房间开始数,直到数到第j个房间为止。
二、通过二维数组的行指针和列指针来引用二维数组元素
通过对二维数组的行地址和列地址的分析可知,二维数组中有两种指针。一种是行指针,使用二维数组的行地址进行初始化;另一种是列指针,使用二维数组的列地址进行初始化。
例如,对于上图所示的二维数组a,可定义如下的行指针:
int (*p)[4];
在解释变量声明语句中变量的类型时,虽然说明符[ ]的优先级高于* ,但由于圆括号的优先级更高,所以先解释* ,在解释[ ] 。所以,p的类型被表示为
p-----------> * ---------------> [4] ------------>int
说明定义了一个可指向含有4个元素的一维整型数组的指针变量。关键字int代表行指针所指一维数组的类型。[ ] 中的4表示指针所指一维数组的长度,它是不可以省略的。实际上,这个指针变量p可作为一个指向二维数组的行指针,它所指向的二维数组的每一行都有4个元素。
注意:在变量声明语句中必须显式地指定指针变量所指向的一维数组的长度(对应于二维数组的列数)。对指向二维数组的行指针p仅从初始化的方法为:p = a;或 p = &p[0];
通过行指针p引用二维数组a的元素a[i][j]的方法可用以下4种等价的形式:
p[i][j]<----------> *(p[i]+j)<----------> *(*(p+i)+j)<---------->(*(p+i))[j]
由于列指针所指向的数据类型为二维数组的元素类型,因此列指针和指向同类型简单变量的指针的定义法是一样的。例如,对于上图的二维数组a,可定义下列指针:
int *p;
p=a[0]; p=*a; p =&a[0][0];
定义了列指针p后,为了能通过p引用二维数组a的元素a[i][j],可将数组a看成一个由(m行×n列)个元素组成的一维数组。由于p代表数组的第0行0列的地址,而从数组的第0行第0列寻址到数组的第i行第j列,中间需跳过i×n+j个元素,因此,p+i*n+j代表数组的第i行第j列的地址,即&a[i][j],*(p+i*n+j)都表示a[i][j]。
注意:此时不能用p[i][j]来表示数组元素,这是因为此时并未将这个数组看出二维数组,而是将二维数组等同于一维数组看待的,也就是将其看成了一个具有mXn个元素的一维数组。正因如此,在定义二维数组的列指针时,无须指定他所指向的二维数组的列数。
例题:编写程序,输入一个3行4列的二维数组,然后输出这个二维数组的元素值。
#include <stdio.h>#define N 4void InputArray(int p[][N],int m,int n);
void OutputArray(int p[][N],int m,int n);
int main(void)
{int a[3][4];printf("Input 3*4 numbers:\n");InputArray(a,3,4);OutputArray(a,3,4);return 0;
}void InputArray(int p[][N],int m,int n)
{int i,j;for(i=0;i<m;i++){for(j=0;j<n;j++){scanf("%d",&p[i][j]);}}
}void OutputArray(int p[][N],int m,int n)
{int i,j;for(i=0;i<m;i++){for(j=0;j<n;j++){printf("%4d",p[i][j]);}printf("\n");}
}
#include <stdio.h>#define N 4void InputArray(int (*p)[N],int m,int n);
void OutputArray(int (*p)[N],int m,int n);
int main(void)
{int a[3][4];printf("Input 3*4 numbers:\n");InputArray(a,3,4);OutputArray(a,3,4);return 0;
}void InputArray(int (*p)[N],int m,int n)
{int i,j;for(i=0;i<m;i++){for(j=0;j<n;j++)scanf("%d",*(p+i)+j);}
}void OutputArray(int (*p)[N],int m,int n)
{int i,j;for(i=0;i<m;i++){for(j=0;j<n;j++)printf("%4d",*(*(p+i)+j));}
}