5.4 指针与数组之间的关系
指针和数组之间究竟有什么样的关系,接下来就通过以下几个方面一一加以讲解。
1.指针对于数组的引用
在前面讲解数组时也提到了指针,但是并没有进行具体的分析,通过指针来引用一维数组也已经多次讲到了,在此就不过多讲解,接下来先通过一段代码来看如何通过指针来引用二维数组。
include<stdio.h>
void main()
{
int a[3][3];
int i,j;
int*pa;
printf("直接引用二维数组a中的元素\n");
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
a[i][j]=i*3+j+1;
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
pa=a[0];
printf("通过指针引用二维数组a中的元素\n");
for(i=0,j=0;pa<=&a[2][2];pa++)
{
printf("a[%d][%d]=%d\t",i,j,*pa);
j++;
if(3==j)
{
j=0;
i++;
printf("\n");
}
}
return;
}
运行结果:
直接引用二维数组a中的元素
a[0][0]=1 a[0][1]=2 a[0][2]=3
a[1][0]=4 a[1][1]=5 a[1][2]=6
a[2][0]=7 a[2][1]=8 a[2][2]=9
通过指针引用二维数组a中的元素
a[0][0]=1 a[0][1]=2 a[0][2]=3
a[1][0]=4 a[1][1]=5 a[1][2]=6
a[2][0]=7 a[2][1]=8 a[2][2]=9
以上代码如何通过指针来成功地引用二维数组中的元素的呢?下面通过图5-7来分析。由于计算机的内存结构是一维的,即线性的,因此计算机在内存中存储多维数组时要将其转换为一维来进行存储。根据一维数组存储时按照下标来标识存储的先后顺序,下标从0开始,多维数组转换为一维数组的方法是从最外维开始转换,之后是次外维,依此类推,直到第一维。
图 5-7 指针对二维数组的引用
前面讲解数组时提到了动态数组的概念,在创建多维动态数组的时候,采用的是多级指针的方法,但是没有提到另外两种动态数组的创建方式,即通过指针数组和数组指针的方式,下面就来看看如何通过它们来创建动态数组。
2.通过指针数组创建动态数组
接下来通过代码介绍如何通过一个指针数组来创建一个动态的二维数组,读者可将其创建方法与本书4.6节介绍的创建方法进行对比。
include<stdio.h>
include<stdlib.h>
define N 3
define M 4
void main()
{
int*a[N];
int i,j;
for(i=0;i<3;i++)
{
if((a[i]=(int)malloc(Msizeof(int)))==NULL)
{
printf("分配失败!");
exit(0);
}
}
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
{
a[i][j]=i*M+j+1;
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
for(i=0;i<N;i++)
free(a[i]);
return;
}
运行结果:
a[0][0]=1 a[0][1]=2 a[0][2]=3 a[0][3]=4
a[1][0]=5 a[1][1]=6 a[1][2]=7 a[1][3]=8
a[2][0]=9 a[2][1]=10 a[2][2]=11 a[2][3]=12
上面的代码中,在起始部分定义了一个整型指针数组,其第一维的长度是已知的,第二维的长度是在运行过程中指定的,相当于定义了一个含有3个元素的数组,每个元素又是一个指针,为其分配一片连续的内存单元,在此为每个元素分配了4个整型变量的存储空间。使用完动态数组之后,最重要的一点就是将其申请的内存空间通过free()函数释放,否则将会造成内存泄露。当然,利用指针数组也可以定义多维数组,定义方法与二维数组类似。
3.通过数组指针创建动态数组
学习了如何通过指针数组创建动态数组后,接下来学习如何通过数组指针创建动态数组。
include<stdio.h>
include<stdlib.h>
define N 3
define M 4
void main()
{
int(*a)[N];
int i,j;
if((a=(int()[N])malloc(NM*sizeof(int)))==NULL)
{
printf("分配失败!");
exit(0);
}
printf("当以4X3二维数组方式引用创建的动态数组时\n");
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
a[i][j]=i*N+j+1;
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
printf("当以3X4二维数组方式引用创建的动态数组时\n");
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
{
a[i][j]=i*M+j+1;
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
free(a);
return;
}
运行结果:
当以4X3二维数组方式引用创建的动态数组时
a[0][0]=1 a[0][1]=2 a[0][2]=3
a[1][0]=4 a[1][1]=5 a[1][2]=6
a[2][0]=7 a[2][1]=8 a[2][2]=9
a[3][0]=10 a[3][1]=11 a[3][2]=12
当以3X4二维数组方式引用创建的动态数组时
a[0][0]=1 a[0][1]=2 a[0][2]=3 a[0][3]=4
a[1][0]=5 a[1][1]=6 a[1][2]=7 a[1][3]=8
a[2][0]=9 a[2][1]=10 a[2][2]=11 a[2][3]=12
从上面的运行结果发现,在采用数组指针创建动态数组的时候,第一维和第二维的分配不是固定的,可以人为地指定,只要第一维和第二维的乘积不超过动态申请时的大小即可,这是用数组指针创建动态数组的特殊之处。
了解了如何通过指针数组和数组指针创建动态数组之后,接下来分析指针数组和数组指针对数组的引用方式。
4.指针数组对数组的引用
关于指针数组怎样引用数组,可以通过下面的代码来加以分析。
include<stdio.h>
include<stdlib.h>
define N 3
define M 4
void main()
{
int*a[M],b[M][N];
int i,j;
for(i=0;i<M;i++)
for(j=0;j<N;j++)
{
b[i][j]=i+j;
}
for(i=0;i<M;i++)
{
a[i]=b[i];
}
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
return;
}
运行结果:
a[0][0]=0 a[0][1]=1 a[0][2]=2
a[1][0]=1 a[1][1]=2 a[1][2]=3
a[2][0]=2 a[2][1]=3 a[2][2]=4
a[3][0]=3 a[3][1]=4 a[3][2]=5
在使用指针数组时要清楚,指针数组的本质是数组,数组中的每个元素都是一个指针,我们可以通过图5-8来了解指针数组的引用方式。通过指针数组来引用数组时,只要把数组中每行起始元素的首地址指给指针数组中的每个元素,就可以实现对数组的引用了。
图 5-8 指针数组对数组的引用
5.数组指针对数组的引用
看完指针数组对于数组的引用,接下来通过下面一段代码来看数组指针对数组的引用。
include<stdio.h>
include<stdlib.h>
define N 3
define M 4
void main()
{
int(*a)[N],b[M][N];
int i,j;
for(i=0;i<M;i++)
for(j=0;j<N;j++)
{
b[i][j]=i+j;
}
a=b;
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("a[%d][%d]=%d\t",i,j,a[i][j]);
}
printf("\n");
}
return;
}
运行结果:
a[0][0]=0 a[0][1]=1 a[0][2]=2
a[1][0]=1 a[1][1]=2 a[1][2]=3
a[2][0]=2 a[2][1]=3 a[2][2]=4
a[3][0]=3 a[3][1]=4 a[3][2]=5
由上面的代码可知,在使用数组指针引用数组时,只需要将数组元素的首地址赋值给定义的数组指针即可,但是要注意,表示数组首地址的方法很多,但不是每种表示方法都可以。&b是数组的首地址,如果执行“a=&b;”这样的赋值操作,那么会出现“error C2440:'=':cannot convert from'int()[4][3]'to'int()[3]'”错误。将“int b[4][3]”变形为“int(&b)[4][3]”,就会清楚地发现,&b相当于int()[4][3]类型,而定义的数组指针是int(*)[3]类型,显然不匹配,因此会出错。
采用“a=&b[0];”这样的赋值方式可以吗?答案是肯定的。与前面分析多维数组的方法相同,b[0]元素相当于一个包含了3个整型变量的一维数组,可将b[0]视为一种特殊的类型int[3],这时再进行变形,将&b[0]视为int(*)[3]类型,与定义的a刚好一致,所以可以进行这样的赋值。有人可能会有疑惑,仅仅将第一行的首地址&b[0]给了a,如何实现取出数组中所有的元素值呢?前面讲过,多维数组在计算机内存中是按照一维的方式进行存储的,并且数组元素存储在一片连续的内存区域中,所以能够逐一取出数组中的元素值。需要注意的是,数组中两个下标的含义是不一样的,在此以二维数组为例进行讲解。
include<stdio.h>
define N 2
define M 3
void main()
{
int a[N][M];
int i,j;
for(i=0;i<N;i++)
for(j=0;j<M;j++)
{
a[i][j]=i+j;
}
printf("直接求取数组元素的地址\n");
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
{
printf("&a[%d][%d]=%d\t",i,j,&a[i][j]);
}
printf("\n");
}
printf("间接求取数组元素的地址\n");
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
{
printf("&a[%d][%d]=%d\t",i,j,a[i]+j);
}
printf("\n");
}
return;
}
运行结果:
直接求取数组元素的地址
&a[0][0]=1245032&a[0][1]=1245036&a[0][2]=1245040
&a[1][0]=1245044&a[1][1]=1245048&a[1][2]=1245052
间接求取数组元素的地址
&a[0][0]=1245032&a[0][1]=1245036&a[0][2]=1245040
&a[1][0]=1245044&a[1][1]=1245048&a[1][2]=1245052
直接求取数组元素地址和间接求取数组元素地址得到的结果一样,下面通过图5-9来分析。数组名和第一维下标决定了一行元素的起始地址,而第二维下标决定了数组元素存储单元相对于行起始地址的偏移量,所以在代码中通过a[i]+j同样可以存储没有数组元素的起始地址。注意,如果写成“a[i][j]”这样的形式,那么就意味着当前取出的是距离起始地址a[i]的偏移量为j的存储单元的数组元素的值,所以第二维的[]的作用和前面所说的*有着相同的作用,都用于得到地址所指向内存单元存储的值。
图 5-9 数组元素的引用