二维数组和指针

要用指针处理二维数组,首先要解决从存储的角度对二维数组的认识问题。我们知道,一个二维数组在计算机中存储时,是按照先行后列的顺序依次存储的,当把每一行看作一个整体,即视为一个大的数组元素时,这个存储的二维数组也就变成了一个一维数组了。而每个大数组元素对应二维数组的一行,我们就称之为行数组元素,显然每个行数组元素都是一个一维数组


下面我们讨论指针和二维数组元素的对应关系,清楚了二者之间的关系,就能用指针处理二维数组了。
设p是指向数组a的指针变量,若有:
p=a[0];
则p+j将指向a[0]数组中的元素a[0][j]。
由于a[0]、a[1]┅a[M-1]等各个行数组依次连续存储,则对于a数组中的任一元素a[i][j],指针的一般形式如下:
p+i*N+j
元素a[i][j]相应的指针表示为:
*( p+i*N+j)
同样,a[i][j]也可使用指针下标法表示,如下:
p[i*N+j]
例如,有如下定义:
int a[3][4]={{10,20,30,40,},{50,60,70,80},{90,91,92,93}};
则数组a有3个元素,分别为a[0]、a[1]、a[2]。而每个元素都是一个一维数组,各包含4个元素,如a[1]的4个元素是a[1][0]、a[1][1]、a[1]2]、a[1][3]。
若有:
int *p=a[0];
则数组a的元素a[1][2]对应的指针为:p+1*4+2
元素a[1][2]也就可以表示为:*( p+1*4+2)
用下标表示法,a[1][2]表示为:p[1*4+2]



特别说明:
对上述二维数组a,虽然a[0]、a都是数组首地址,但二者指向的对象不同,a[0]是一维数组的名字,它指向的是a[0]数组的首元素,对其进行“*”运算,得到的是一个数组元素值,即a[0]数组首元素值,*a等价于a[0]    a[0]等价于&a[0][0],因此,*a[0]与a[0][0]是同一个值;而a是一个二维数组的名字,它指向的是它所属元素的首元素,它的每一个元素都是一个行数组,因此,它的指针移动单位是“行”,所以a+i指向的是第i个行数组,即指向a[i]。对a进行“*”运算,得到的是一维数组a[0]的首地址,即*a与a[0]是同一个值。当用int *p;定义指针p时,p的指向是一个int型数据,而不是一个地址,因此,用a[0]对p赋值是正确的,而用a对p赋值是错误的。这一点请读者务必注意。
⑵ 用二维数组名作地址表示数组元素。
另外,由上述说明,我们还可以得到二维数组元素的一种表示方法:
对于二维数组a,其a[0]数组由a指向,a[1]数组则由a+1指向,a[2]数组由a+2指向,以此类推。因此,*a与a[0]等价、*(a+1)与a[1]等价、*(a+2)与a[2]等价,┅,即对于a[i]数组,由*(a+i)指向。由此,对于数组元素a[i][j],用数组名a的表示形式为:
*(*(a+i)+j)
指向该元素的指针为:
*(a+i)+j
数组名虽然是数组的地址,但它和指向数组的指针变量不完全相同。指针变量的值可以改变,即它可以随时指向不同的数组或同类型变量,而数组名自它定义时起就确定下来,不能通过赋值的方式使该数组名指向另外一个数组。
例4 求二维数组元素的最大值。


该问题只需对数组元素遍历,即可求解。因此,可以通过顺序移动数组指针的方法实现。

main()
{
    int a[3][4]={{3,17,8,11},{66,7,8,19},{12,88,7,16}};
    int *p,max;
    for(p=a[0],max=*p;p<a[0]+12;p++)
       if(*p>max)
          max=*p;
    printf("MAX=%d/n",max);
}


执行结果:
MAX=88
这个程序的主要算法都是在for语句中实现的:p是一个int型指针变量;p=a[0]是置数组的首元素地址为指针初值;max=*p将数组的首元素值a[0][0]作为最大值初值;p<a[0]+12是将指针的变化范围限制在12个元素的位置内;p++使得每比较一个元素后,指针后移一个元素位置。


例5 求二维数组元素的最大值,并确定最大值元素所在的行和列。
本例较之上例有更进一步的要求,需要在比较的过程中,把较大值元素的位置记录下来,显然仅用上述指针移动方法是不行的,需要使用能提供行列数据的指针表示方法。

main()
{
    int a[3][4]={{3,17,8,11},{66,7,8,19},{12,88,7,16}};
    int *p=a[0],max,i,j,row,col;
    max=a[0][0];
    row=col=0;
    for(i=0;i<3;i++)
       for(j=0;j<4;j++)
         if(*(p+i*4+j)>max)
         {
            max=*(p+i*4+j);
            row=i;
            col=j;
         }
    printf("a[%d][%d]=%d/n",row,col,max);
}


程序运行结果:
a[2][1]=88


⑶ 行数组指针
在上面的说明中我们已经知道,二维数组名是指向行的,它不能对如下说明的指针变量p直接赋值:
int a[3][4]={{10,11,12,13},{20,21,22,23},{30,31,32,33}},*p;
其原因就是p与a的对象性质不同,或者说二者不是同一级指针。C语言可以通过定义行数组指针的方法,使得一个指针变量与二维数组名具有相同的性质。行数组指针的定义方法如下:
数据类型 (*指针变量名)[二维数组列数];
例如,对上述a数组,行数组指针定义如下:
int (*p)[4];
它表示,数组*p有4个int型元素,分别为(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3] ,亦即p指向的是有4个int型元素的一维数组,即p为行指针 
    指针p为指向一个由4个元素所组成的整型数组指针。在定义中, 圆括号是不能少的, 否则它是指针数组。这种数组的指针不同于前面介绍的整型指针, 当整型指针指向一个整型数组的元素时, 进行指针(地址)加1运算, 表示指向数组的下一个元素, 此时地址值增加了4(因为放大因子为4), 而如上所定义的指向一个由3个元素组成的数组指针, 进行地址加1运算时, 其地址值增加了16(放大因子为4x4=16), 这种数组指针使用得较少, 但在处理二维数组时, 还是很方便的。例如:
          int a[3][4], (*p)[4];
          p=a;
    开始时p指向二维数组第0行, 当进行p+1运算时, 根据地址运算规则, 此时放大因子为4x4=16, 所以此时正好指向二维数组的第1行。和二维数组元素地址计算的规则一样, *p+1指向a[0][1], *(p+i)+j则指向数组元素a[i][j]。
     例1
    

#include <stdio.h>
int main() 
{ 
	int a[3] [4]={ 
		{1,3,5,7}, 
		{9,11,13,15}, 
		{17,19,21,23} 
	}; 

	int i,(*b)[4]; 

	b=a+1;
	/* b指向二维数组的第1行, 此时*b[0]或 **b是a[1][0] */ 
	for(i=1;i<=4;b=b[0]+2,i++)/* 修改b的指向, 每次增加2 */ 
		printf("%d\t",*b[0]); 
	printf("\n"); 

	for (i=0; i<2; i++) { 
		b=a+i;                  /* 修改b的指向, 每次跳过二维数组的 一行 */ 
		printf("%d\t",*(b[i]+1)); 
	} 

	printf ("\n"); 

	return 0;
} 



    程序运行结果如下:
     9    13   17   21
     3    11   19

*****************************************************************************************************************************************************************************

下面说明一下,我碰到的问题,我们定义了一下如下的函数:void function(double** array,int width,int height)

然后我们定义了一个二维数组 double p[3][3]={{1,2,3},{4,5,6},{7,8,9}};

当我们调用function时,即function(p,3,3),编译器会报错:

error C2664: 'function' : cannot convert parameter 1 from 'double [3][3]' to 'double **'
参数传递实际上是一个赋值的过程,为了便于说明我们底下都以赋值的方式加以说明。

由分割线当中的知识我们知道p是数组首地址,地址指向的是第一个行数组,在某种程度上来说可以把二维数组名理解为指针的指针,但是这两者是有区别的。我们回顾一下一维数组和指针变量:

double p[]={1,2,3};

double* pp=p;

这个是没有任何问题,正是因为这个操作,使得很多人认为数组名就是指针,是同一种类型。其实这是不对的。在这里很多人喜欢纠结于他们的硬件实现,认为它们的实现是差不多的,所以没有差别,这种自以为是的想当然的想法是不可取的。基本类型是C++提供,如果C++认为这是两种不同的类型,那它们就是两种不同的类型,因为我们基于C++编程,而不是基于硬件。实际上上述的赋值之所以能够实现是因为C++中提供了一种自动型转化的机制,数组在运算时会自动转化为指针类型,所以上述的赋值过程实际上是p先转为指针类型之后才赋值给pp的。另外需要注意的是这种自动转化是单向的,指针是无法自动转化为数组的,所以用指针给数组变量赋值是错误的。

这里又出现了一个新的问题,那既然一维数组可以转化为指针变量,那为什么二维数组就不可以转化为指向指针的指针变量呢?难道C++只提供了直接的转化,而不支持间接的转化(这里的二维数组的转化和一维数组相比好像是又多了一层转化)?

在回答这个问题之前,我们先看看如下两种类型:

double *p[3]和double(*p)[3]

‍double *p[3]是一个指针数组,它是一个数组,里面存放了3个指针;‍double(*p)[3]它是一个数组指针,它是一个指针,这个指针指向的是一个数组,它和二维数组有相同的性质,由此我们可以知道如下的赋值是可行的:

double p[3][3]={{1,2,3},{4,5,6},{7,8,9}};

double(*pp)[3]=p;

这里实际上的话也是应该执行了一次上面所说的自动转化,p转化了一个指向行数组的指针,然后赋值给了数组指针变量pp;

另外,我们发现底下的赋值也是可行的:

double *p[3];

double **pp=p;

这里实际上也是执行了一次上面所说的转化,p转化了一个指向指针变量的指针,然后赋值给了pp;

这里看下,上面两次转化后唯一的的区别于一个是指向数组的指针,一个是指向指针的指针,而C++不允许接下来的再次转化,说明C++只支持数组到指针的一次转化,而二次转化没有支持。这样就回答了上面的问题。那为什么C++不支持呢?(这个问题好难回答!)

底下我们再来看下,double *p[3]和double(*p)[3],这两个是不同的类型,如果C++支持二次转化的话,那么double *p[3]和double(*p)[3]就可以视为一种类型了,所以看来C++不支持两次转化,也是出于多方面的考虑。这个可能也是C++考虑的因素之一吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值