目录
一、从本质了解二维数组
二、访问二维数组的方式
1、指向元素的指针
2、指向每一行的指针(指针数组)
3、指向整个数组的指针(数组指针)
三、3种二维数组的形参声明方式
1、数组法
2、数组指针法
3、指针法
在实践工程项目中,往往需要在函数形参中声明多维数组,特别是二维数组,本文介绍几种方法进行声明二维数组的形参。
一、从本质了解二维数组
二维数组,实际上也是一维数组,只不过这个一维数组的每个元素都是一个一维数组。因此,将二维数组的每一行看做一个元素,很容易可以知道二维数组中各元素在内存中是按行优先进行连续存储的,如定义数组A[3][4],那么它在内存中的存储情况如下:
由此也可得到二维数组中元素A[i][j]的地址为&A[i][j]=A+L*(C*i+j),其中A为二维数组A的标识符(数组名),也就是数组的首地址,L为数组元素的数据类型,C为二维数组的列数。由此可见,要知道二维数组中某一元素的地址,就必须知道数据类型大小以及二维数组的列数,这样最终才能实现对二维数组元素的访问,这也是为什么二维数组的定义必须指定列数。
从以上可知,二维数组声明时必须指定列数,否则报错,如下所示:
//函数声明时
void TwoDimensionalArray(int[][], int a); //error
//变量定义时
int a1[][];//error
int a2[2][];//error
int a3[1][2]; //right
int a4[][3];//right
二、访问二维数组的方式
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、指向每一行的指针(指针数组)
这种方式是定义指针来指向二维数组的某一行,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p=a[0];
实际上,二维数组实际上是一维数组中每个元素都为一个一维数组,那么此时定义了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)[4]=a;
在这种定义方式下,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是以指针作为元素的数组,也就是指针数组。
值得注意的是,这种方法必须知道二维数组的行数。
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)了。
三、3种二维数组的形参声明方式
1、数组法
该方法需要指定行数:
void display2DArray(int arr[][5],int row);//需要指定行数row
注意:需要指定行数,但是以下指定方式是无效的:
//以下方式指定行数无效
void display2DArray(int arr[3][5]);
原因:如同一维数组在函数形参中声明一样,数组被弱化为指针,因此函数体内部无法获取该值。如下所示:
//以下两种声明方式是等效的:
void display(int array[5])//5在这里是无效的
void display(int array[])//5在这里是无效的
2、数组指针法
该方法需要指定行数:
void display2DArray(int (*arr)[5],int row);
注意:切记以指针数组声明,该声明是错误的,如下:
void display2DArray(int *array[5]); //error
3、指针法
该方法需要同时指定列数与行数,以下展示了该方法的声明与调用过程:
void display2DArray(int *array,int rows,int cols) //error
{
for (int i=0;i<rows;++i)
{
for (int j=0;j<cols;++j)
{
printf("%d ", *(array + rows * i + j));
}
}
}
参考文献:https://blog.csdn.net/qq_28114615/article/details/86434837
《深入理解C指针》