目录
1.数组元素的访问
数组中的各元素在内存中是连续分布的,要想访问数组中某一元素,那么就必须知道其地址。
在一维数组中,数组A的元素A[i]的地址&A[i]=A+L*i,其中A为数组的标识符(数组名),也可以用A来代表数组的首地址,L为数组A的数据类型,由此可见,对于一维数组,只需要知道数据类型大小和索引i,就可以知道A[i]的地址,从而就可以访问A[i]了,这也是为什么一维数组的定义可以不指定数组大小,也不会妨碍数组元素的访问。
二维数组,实际上也是一维数组,只不过这个一维数组的每个元素都是一个一维数组。因此,将二维数组的每一行看做一个元素,很容易可以知道二维数组中各元素在内存中是按行优先进行连续存储的,如定义数组A[3][4],那么它在内存中的存储情况如下:
由此也可得到二维数组中元素A[i][j]的地址为&A[i][j]=A+L*(C*i+j),其中A为二维数组A的标识符(数组名),也就是数组的首地址,L为数组元素的数据类型,C为二维数组的列数。由此可见,要知道二维数组中某一元素的地址,就必须知道数据类型大小以及二维数组的列数,这样最终才能实现对二维数组元素的访问,这也是为什么二维数组的定义必须指定列数。
2.通过指针访问数组
在理解访问数组的指针之前,我们不得不先理解另一个问题:如果定义一个数组A,按前面所说,A就是数组第一个元素的首地址,那么A+1是什么意思呢?我在第一次遇到这个问题的时候,第一反应是A既然表示的是地址,那么A+1自然就是地址+1了呀!然而事实并非如此,我们先来做个测试如图所示:
根据测试可知,a+1并非就是a数值上加1,a+2也并非是a数值上加上2,他们实际上两两之间加的是4,即是sizeof(int),而*a=a[0]=1,*(a+1)=a[1]=2.....由此可以知道,a+i实际上就是a的第i个元素的地址,这与前面&A[i]的地址计算是相匹配的。
而对于二维数组来说,道理其实是一样的,不过二维数组的元素A[0]表示第一行,A[1]表示第二行......因此,二维数组中A是数组的首地址,也就是第0行A[0]的行首地址,A+1就是第1行A[1]的行首地址,.....,A+i就是第i行A[i]的行首地址了....
2.1 通过指针访问一维数组
一维数组指针的定义方式如下:
int a[4]={1,2,3,4};
int *p=a;
这里定义了一个指针变量p,它指向一个整型变量,而a实际上也就是a的第一个元素a[0]的地址,因此p就指向了数组的第一个元素a[0]。 那么p+1等于什么呢?实际上,p+1在数值上也就等于a+1,因此,p+1其实就是a[1]的地址,p+i就是a[i]的地址,这样,就可以通过*(p+i)来访问a[i]的值了。如图所示:
由此可以得出,对于一维数组的数组指针p,数组名p实际上是指向数组第一个元素的指针,即p为int *类型,由于其指向int型数据,因此(p+i)就相当于在p的基础上偏移了i*sizeof(int)的地址大小,就等于数组第i个元素的地址(i=0,1,2....)。
2.2 通过指针访问二维数组
2.2.1 指向元素的指针
这种办法可以说是最直接的办法了,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p=&a[0][0];
在这种定义方式下,对于数组元素的访问就需要一个个元素往后推,结合本文开头所说的,二维数组是按行优先存储的,第i行第j列元素实际上是整个二维数组中的第i*c+j个元素,其中c为二维数组的列数,相对于第一个元素,第i*c+j个元素的地址偏移量为4*(i*c+j),而由于p为int指针,p+x的地址偏移量为p+4*x,因此a[i][j]=*(p+i*c+j)。
2.2.2 指向每一行的指针(指针数组方式)
这种方式是定义指针来指向二维数组的某一行,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p=a[0];
这里可能会有些迷惑了,为什么这里的指针变量p是指向的a[0]呢?而不能让p指向a呢?
实际上,前面说过,二维数组实际上是一维数组中每个元素都为一个一维数组,那么此时定义了a[3][4],a代表什么呢?一定要知道,不管数组a是几维的,它实际上都是一维的,a一定是数组第一个元素的地址,这里的第一个元素是指的将n维数组看做一维数组后的第一个元素。因此,在a[3][4]中,将a看做一维数组后它的每一个元素实际上是每一行,因此如果让指针变量p指向a,就相当于让p指向第0行,而这里的第0行并不是整形变量,因此不能让p指向a;
而如果让p指向a[0],实际上就相当于指向a[0]的第一个元素,也就是a[0][0]了,此时的a[0][0]是整形变量,因此让p指向a[0]是可以的,当然,也可以让p指向a[1]、a[2].....如果让p指向第i行,那么此时的p+j就相当于是一维数组中第j个元素的地址了,即p+j就指向了a[i][j]。
这样看来,对于有r行的二维数组,就需要定义r个指针指向每一行,这样其实就可以定义一个装有r个指针的数组,其中每一个指针分别指向二维数组的每一行,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int *p[3]; for(int i=0;i<3;i++)p[i]=a[i];
在这种定义方式下,p[i]即指向数组的第i行,也就是a[i][0],知道了a[i][0]的地址,要想访问a[i][j],地址即是a[i][0]+4*j,这里由于p[i]是int型的指针变量,因此刚好就等于p[i]+j,即a[i][j]=*(p[i]+j)。这就是一种定义指向二维数组的指针的方式,这里的p是以指针作为元素的数组,也就是指针数组。
值得注意的是,这种方法必须知道二维数组的行数。
2.2.3 指向整个数组的指针(数组指针方式)
这种方式是定义指针来指向整个数组,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4]=a;
那么这里的p是什么意思呢?如果看不懂(*p)[4],那就把*p用b去替换掉,就成了b[4],这里的b就很明显了,就是一个有4个元素的数组的首地址,那么这里的p的含义也就不难得出了:指向一个有四个元素的数组的首地址,尤其需要注意的是,这里p是指向首地址,并非就是首地址,*p才是首地址,也就是指向第一个元素,因此这里的p也就是指向指针的指针。在int (*p)[4]=a中,p就是指向了第一个含四个元素的数组的首地址,也就是p指向a的首地址也就是a[0],*p指向a[0]的首地址也就是a[0][0],要访问a[0][0],就就是*(*p)了;
既然p是指向一个有四个元素的数组首地址的指针,那么p+i呢?*p+i呢?要分析清楚这个问题,我们还是参考b[4],前面说过,这里的p是指向首地址的指针,因此p+i就对应了&b+i,因此p+i就指向了&b+i所在的位置;*p是b的首地址,指向b的第一个元素,*p+i就对应了b+i,因此*p+i就指向了b+i所在的位置。
对于p+i,我们来看看&b+i和&b之间偏移了多少:
可以看到,&b+1偏移了16个字节,&b+2偏移了32个字节,而16个字节刚好就是数组b的大小,因此&b+i实际上偏移的地址为i*sizeof(b)=i*c*sizeof(int)。也就是说p+i会跳过整个包含四个整型元素的数组指向下一个包含四个元素的数组,回到a[3][4]中,p指向的是a[0],那么p+1就指向a[1],p+i就指向a[i]了,因此,p+i偏移i个b数组大小的地址,也就是指向二维数组的第i行。
而对于*p+i,就比较简单了,可以看做*p=b,那么*p+i也就是b+i,即是第i个元素的地址,因此也可得出*p+i指向当前有四个元素的数组的第i个元素。
好了,那么我们要是想访问数组中的a[i][j]怎么办呢?前面说过,要访问数组中的元素,必须先得到其地址,对于a[i][j],它是a数组中第i行的子数组中的第j个元素,综合上面分析的,p+i即指向了数组a的第i行,*(p+i)就是该行的首地址,也就是指向了a[i][0],而对于a[i]这个一维数组的第j个元素当然就是a[i]+j,也就是*(p+i)+j了,因此,a[i][j]的地址就是*(p+i)+j,a[i][j]=*(*(p+i)+j)。
3 总结
综合以上分析,总结一下用指针访问数组元素的方法(以数组a为例):
|
一维数组a[ ] |
二维数组a[R ][ C ] |
||
定义方式 |
T *p=a; |
T *p=&a[m][n]; |
T *p[R]; p[m]=a[m]; (m=0,1..R-1) |
T (*p)[C]=a; |
访问方式 |
a[i]=*(p+i) |
a[i][j]=*(p+(i-m)*C+(j-n)) |
a[i][j]=*(p[i]+j); |
a[i][j]=*(*(p+i)+j) |
转载自原文链接, 如需删除请联系管理员。
原文链接:指针数组、数组指针——用指针访问数组方法总结,转载请注明来源!