《C语言程序设计》指针篇<二>

通过指针引用多维数组

如何理解二维数组元素的地址?

要知道,这本书用了整整两页的内容来讲解这方面的知识,从这里足以看出来理解通过指针来引用二维数组是一件比较麻烦的事情,但是我认为理解并不难。

  • 什么是二维数组?
    举个例子:int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a是二维数组名,可以看成一个大数组“包着”三个小数组:
    大数组(管理小数组的数组)a包含三个元素(小数组):a[0],a[1],a[2]。小数组a[0]包括四个元素:1,3,5,7。小数组a[1]包括四个元素:9,11,13,15。小数组a[2]包括四个元素:17,19,21,23。

    二维数组:“小数组的大数组”

一.数组名a和&a[i]

  • 回到我们指针的内容,之前我们理解过,数组名代表该数组首元素的地址(&a[0]),那么这里的二维数组名也可以这样子理解为是&a[0],但是我们上面提到,a[0]也是一个数组,那么&a[0]代表的是什么意思呢?事实上,它是一种地址的计算方法,得到的答案是小数组a[0]首元素的地址。

  • 从我上面所述的二维数组是“小数组的大数组”观点来看,数组名a与二维数组首元素的地址(&a[0])等价,但是由于这里首元素a[0]是一个小数组,我们可以看作a指向大数组的第一个元素:小数组a[0] 的第一个元素a[0][0]。也就是说,数组名a的值是&a[0][0]

  • 类比可得:&a[i]就是小数组a[i]首元素的地址。

二.a+i是什么?

  • 根据前面的知识,我们大概可以猜出a+1是地址,那么它所指向的内存是什么呢?
  • 前文提到:a指向大数组的第一个元素:小数组a[0] 的第一个元素a[0][0]。那么a+1则指向大数组的第二个元素:小数组a[1] 的第一个元素a[1][0],或者说a+1是小数组a[1]的首元素的地址。从内存来看,假定内存名为a的内存(存放&a[0][0])的地址是2000,a+1的值则是2000+4×4=2016。
  • 以此类推,a+i指向大数组的第i+1个元素:小数组a[i] 的第一个元素a[i][0]
  • 由此可以得出以下重要的等价关系:

1.a+i与&a[i](实际上是&a[i][0])等价。

2.*(a+i)与a[i]等价(二者都是地址)。

三.a[i]+j是什么?

  • 一维数组int a[3]={1,2,3}中,a+1(指针:存放a[1]的地址)指向a[1],即1.a+1与&a[1]等价。2.*(a+1)与a[1]等价。
  • 那么在一维数组中,1.a+i与&a[i]等价。 2.*(a+i)与a[1]等价。
  • 回到我们的二维数组,a[i]是小数组名,同理有如下等价关系,请务必记住:

1.a[i]+j与&a[i][j]等价

2.*(a[i]+j)与a[i][j]等价

3.由上文二.2和三.2的等价关系可得:*(*(a+i)+j)与a[i][j]等价。

理解上面很抽象的内容是不是很痛苦呢,我在接触指针的时候也绕了很多弯路,这里给大家的建议还是放慢脚步,多回归课本,不急于求成。

*&的故事

  • 上面提到,(a+i)管理着小数组,它的元素小数组a[0],a[1],a[2]管理着整形变量元素a[0][0]...a[2][3]。
  • 在“指针”这家公司,有普通的“职员”:整形变量。“小管家”:a[i]。还有“总管”:(a+i)。
  • 那么,*有指向的意思,如果在(a+i)前面加上*,它就由管理小数组的“总管”,变成了管理整形变量元素的“小管家”,这次改变相当于“降级”了。
  • 同理,在管理整形变量元素的“小管家”a[i]前面加上&,就升级为“总管”,管理着小数组。
  • 升降级这些变化都是在指针公司里面进行的,大家在职位变化完的第二天,依然以指针员工的身份愉快的工作着。
  • 通过以上的一则故事,我们可以知道这些东西:

1.在(a+i)(指针)前面加上一个**(a+i)就是a[i](指针),那么*(a+i)+j就是a[i]+j,指向a[i][j]

2.在a[i](指针)前面加上一个&&a[i]就是(a+i)(指针),那么&a[i]+j就是(a+i)+j,指向a[i+j]

3.以上是指针到指针的变化。

我们再回到二维数组指针的基类型

在多维数组中,指针变量有两种类型:1.指向数组元素的指针变量2.指向n个元素组成的一维数组的指针变量
关于指向数组元素的指针变量,相信大家在复习前面的内容以后都好理解,这里来看一个例子。

  • 代码1:
#include<stdio.h>
int main()
{
	int *p;
	int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	int i;
	
	p=a[0];/*p初始化指向a[0][0]*/ 
	
	for(p=a[0];p<a[0]+12;p++)
	printf("%d ",*p);
	return 0;
}

输出结果:

上面是按顺序输出数组各元素的值,这里介绍如何输出某个指定的数值元素。
假设有一个n×m的二维数组,计算a[i][j]在数组中的相对位置:i*m+j 其中m是n×m矩阵的列数。
那么a[i][j]的地址就等于&a[0][0]+i*m+j

  • 代码2:
#include<stdio.h>
int main()
{
	int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	int *p=a[0];/*或者:int *p=&a[0][0]*/
	
	/*要求:输出a[1][2]的值*/
	
	/*法1:printf("%d",*(a[1]+2));*/
	
	/*法2:printf("%d",*(*(a+1)+2));*/
	
	printf("%d",*(p+1*4+2));/*法3:利用相对位置*/
	
	return 0;
}

再来看指向一维数组的指针变量。

  • 代码3(片段):

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p1)[4];/*这个定义指针的方式在下文会提到,注意它和定义指针数组(int *p1[4])的区别*/
int *p2;
p1=a;/*指针p1指向一维数组a[0]*/
p2=a[0];/*指针p2指向数组元素a[0][0]*/
 

在上面的代码3里,p1是指向一维数组a[0]的指针,p2是指向数组元素a[0][0]的指针。

a[0],a[1],a[2],p2的基类型是int *(指向整形变量)
而二维数组名a,p1的基类型是int(*)[4](指向包含四个整形元素的一维数组)。

下面介绍指向一维数组指针的定义方法。

指向一维数组指针的定义

上文中的代码1定义的指针变量p是指向变量或者数组元素,现在定义指向一维数组的指针。

  • 代码1(改):输出数组元素的值。
#include<stdio.h>
int main()
{
     int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
     int (*p)[4];/*定义指向一维数组的指针p*/
     int i,j;
     p=a;/*不能写成p=&a;*/
     for(i=0;i<3;i++)
     {
         for(j=0;j<4;j++)
         printf("%d ",*(*(p+i)+j));

         printf("\n");
      }
     return 0;
}

输出结果:

  • int (*p)[4];表示定义指针变量p,指向包含四个元素的一维数组。这里(*p)两边的括号不能去掉。如果去掉的话,*p[4]方括号运算顺序级别高,p先和方括号结合再与前面的*结合,*p[4]就是指针数组。有关指针数组详见下文。

此时,代码段p=a;a[0](一维数组)的地址赋值给p(该一维数组的初始地址&a[0][0])。

  • 这里p只能指向一维数组而不能指向一维数组里的元素,来看反例:
#include<stdio.h>
int main()
{
    int a[4]={1,2,3,4};
    int (*p)[4];
    p=a;/*错误*/

    printf("%d",(*p)[3]);/*输出a[3]*/
    return 0;
}

编译错误,我们定义指向一维数组的指针p,p只能指向一维数组,而这里的错误是把一维数组a的首元素地址赋值给了p,从而p指向了a[0],即指向了整形元素。

正确的赋值方法应该是:p=&a; 关于*和&请看上文。

posted @ 2016-02-05 16:13  Wasdns  阅读(271)  评论(1编辑  收藏  举报