指向多维数组的指针变量

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

指向多维数组的指针变量
1 多维数组的指针
多维数组可以看作是一维数组的延伸,多维数组的内存单元也是连续的内存单元。

换句话说,C语言实际上是把多维数组当成一维数组来处理的。

下面以二维数组为例说明这个概念。

比如,现在有一个int型的二维数组a[3][4],计算机认为这是一个一维的数组a[3],数组的三个元素分别是a[0],a[1]和a[2]。

其中每个元素又是一个一维数组,例如a[0]又是一个包含a[0][0],a[0][1],a[0][2]和a[0][3]共4个元素的数组。

如果我们要引用数组元素a[1][2],可以首先根据下标1找到a[1],然后在a[1]中找到第3个元素a[1][2]。

假设数组a的起始地址为FF10,对应的内存情况如图:
DS:FF10 a[0][0] DS:FF12 a[0][1] DS:FF14 a[0][2] DS:FF16 a[0][3]
DS:FF18 a[1][0] DS:FF1A a[1][1] DS:FF1C a[1][2] DS:FF1E a[1][3]
DS:FF20 a[2][0] DS:FF22 a[2][1] DS:FF24 a[2][2] DS:FF26 a[2][3]
可以看到,二维数组a[3][4]的12个元素在内存中是顺序排列的,因此&a[0][0]是数组第一个元素的地址,为FF10。

a[0][0],a[0][1]这些数组元素都有具体的内存单元值。

但是,a[0],a[1]和a[2]这三个数组元素不占用内存单元,它们只是代号(其实就是一个指针常量),是虚拟的东西。

a[0]本身又是一个数组,包含a[0][0],a[0][1],a[0][2]和a[0][3],那么a[0]作为数组名称,按照C语言的语法,a[0]就是数组首地址,也就是数组第一个元素的地址,即a[0][0]的地址FF10。

同理,a[1]是第一个元素a[1][0]的地址,即FF18;a[2]是第一个元素a[2][0]的地址,即FF20。

相对于a[0],a[1]和a[2]来说,a也是数组的名称,是数组的首地址,即FF10。

a是指向数组首地址的指针,a+1代表什么?a是数组名称,a[0],a[1]和a[2]是元素,那么a+1就是&a[1](如果一个一维数组int b[3],那么b+1是不是&b[1]?),即第二行元素的首地址,指针值为FF18。

同理,a+2就是&a[2],指针值为FF20。

也可以换个角度去理解。

a是数组首地址,指向二维数组第一行的首地址;计算a+1时,指针a跳过的是整个a[0],a+1指向二维数组第二行的首地址。

指针a在做加法时,跳过的是一行元素。

a[1]+1代表什么?指针a[1]是一维数组a[1]中第一个元素a[1][0]的地址FF18。

对于一维数组a[1]来说,指针a[1]+1指向了a[1]中第二个元素a[1][1]的地址,即FF1A。

这里指针加法的单位是一列,每次跳过a[1]中的一个数组元素。

*a代表什么?*a也就是*(a+0),数组第一个元素的值,即a[0]。

前面讲过,a[0]是一个代号,它不是一个具体元素的值,而是内嵌的一维数组a[0]的名字,a[0]本身也是一个指针值。

同理,*(a+1)就是a[1],*(a+2)就是a[2]。

如果要访问二维数组里第i行第j列的某个元素,可以直接引用a[i-1][j-1],也可以使用指针的方法。

指向第i行第j列元素的指针为a[i]+j,或者*(a+i)+j,因此,间接用指针引用二维数组里第i行第j列的某个元素,可以表示为*(a[i]+j)或者*(*(a+i)+j)。

特别需要指出的是,a[i]不是一个具体的数组元素,它是一个代号,实际上是一个指针值,代表i+1行里中第一个元素的首地址。

因此&a[i]不能直接理解为a[i]的物理地址,a[i]不是变量,不存在物理地址;&a[i]代表第i+1行的行指针值,和a+i等价。

同样,*a[i]也不能理解为对一个数组元素进行指针运算符操作。

a[i]是一个指针值,是i+1行中第一个元素的首地址,那么*a[i]就是i+1行里中第一个元素的值,即a[i][0]。

总之,二维数组a[i][j]的指针由于出现了虚拟的a[i]这个概念,所以对于初学者很不好理解,请读者仔细消化理解。

下表是上述概念的综合:
main()
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12},i,j;
/*打印a*/
printf("\na=%X", a);
/*打印a+i, &a[i]*/
for(i=0;i<3;i++)
printf("\na+%d=%X, &a[%d]=%X", i,a+i,i,&a[i]);
/*打印a[i], *(a+i)*/
for(i=0;i<3;i++)
printf("\na[%d]=%X, *(a+%d)=%X", i,a[i],i,*(a+i));
/*打印a[i]+j, *(a+i)+j, &a[i][j]*/
for(i=0;i<3;i++)
for(j=0;j<4;j++)
printf("\na[%d]+%d=%X, *(a+%d)+%d=%X, &a[%d][%d]=%X",
i,j,a[i]+j, i,j,*(a+i)+j, i,j,&a[i][j]);
/*打印**(a+i), *a[i]*/
for(i=0;i<3;i++)
printf("\n**(a+%d)=%X, *a[%d]=%X", i, **(a+i),i, *a[i]);
/*打印*(a[i]+j), *(*(a+i)+j), a[i][j]*/
for(i=0;i<3;i++)
for(j=0;j<4;j++)
printf("\na[%d]+%d=%d, *(a+%d)+%d=%d, &a[%d][%d]=%d",
i,j,*(a[i]+j), i,j,*(*(a+i)+j), i,j,a[i][j]);
}
程序运行的结果为(打印的地址值是动态分配的,不同环境下可能不同;但数组的地址偏移量是相同的):
a=FFBE
a+0=FFBE, &a[0]=FFBE
a+1=FFC6, &a[1]=FFC6
a+2=FFCE, &a[2]=FFCE
a[0]=FFBE, *(a+0)=FFBE
a[1]=FFC6, *(a+1)=FFC6
a[2]=FFCE, *(a+2)=FFCE
a[0]+0=FFBE, *(a+0)+0=FFBE, &a[0][0]=FFBE
a[0]+1=FFC0, *(a+0)+1=FFC0, &a[0][1]=FFC0
a[0]+2=FFC2, *(a+0)+2=FFC2, &a[0][2]=FFC2
a[0]+3=FFC4, *(a+0)+3=FFC4, &a[0][3]=FFC4
a[1]+0=FFC6, *(a+1)+0=FFC6, &a[1][0]=FFC6
a[1]+1=FFC8, *(a+1)+1=FFC8, &a[1][1]=FFC8
a[1]+2=FFCA, *(a+1)+2=FFCA, &a[1][2]=FFCA
a[1]+3=FFCC, *(a+1)+3=FFCC, &a[1][3]=FFCC
a[2]+0=FFCE, *(a+2)+0=FFCE, &a[2][0]=FFCE
a[2]+1=FFD0, *(a+2)+1=FFD0, &a[2][1]=FFD0
a[2]+2=FFD2, *(a+2)+2=FFD2, &a[2][2]=FFD2
a[2]+3=FFD4, *(a+2)+3=FFD4, &a[2][3]=FFD4
**(a+0)=1, *a[0]=1
**(a+1)=5, *a[1]=5
**(a+2)=9, *a[2]=9
a[0]+0=1, *(a+0)+0=1, &a[0][0]=1
a[0]+1=2, *(a+0)+1=2, &a[0][1]=2
a[0]+2=3, *(a+0)+2=3, &a[0][2]=3
a[0]+3=4, *(a+0)+3=4, &a[0][3]=4
a[1]+0=5, *(a+1)+0=5, &a[1][0]=5
a[1]+1=6, *(a+1)+1=6, &a[1][1]=6
a[1]+2=7, *(a+1)+2=7, &a[1][2]=7
a[1]+3=8, *(a+1)+3=8, &a[1][3]=8
a[2]+0=9, *(a+2)+0=9, &a[2][0]=9
a[2]+1=10, *(a+2)+1=10, &a[2][1]=10
a[2]+2=11, *(a+2)+2=11, &a[2][2]=11
a[2]+3=12, *(a+2)+3=12, &a[2][3]=12
从输出的结果可以看到,&a[i]和a[i]的输出值都是一样的。

第i+1行的首地址是FFBE,第i+1行、第1列元素的首地址也是FFBE,这并不矛盾,因为二者都是一个指针值。

但是,二者的含义是截然不同的,指针加法的偏移量也是不同的:前者加1指针跳到下一行;后者加1指针跳到下一列。

2 指向多维数组的指针变量
可以定义指针变量,指向多维数组及其元素。

下面仍以二维数组为例进行说明。

二维数组在内存中是连续的内存单元,我们可以定义一个指向内存单元起始地址的指针变量,然后依次拨动指针,这样就可以遍历二维数组的所有元素。

例如:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0},*p;
inti;
for(p=*a;p<*a+2*3;p++)
printf("\n%f ",*p);
}
结果输出:
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
在上述例子中,定义了一个指向float型变量的指针变量。

语句p=*a将数组第1行,第1列元素的地址赋给了p,p指向了二维数组第一个元素a[0][0]的地址。

根据p的定义,指针p的加法运算单位正好是二维数组一个元素的长度,因此语句p++使
得p每次指向了二维数组的下一个元素,*p对应该元素的值。

根据二维数组在内存中存放的规律,我们也可以用下面的程序找到二维数组元素的值:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0},*p;
inti,j;
printf("Please input i =");
scanf("%d", &i);
printf("Please input j =");
scanf("%d", &j);
p=a[0];
printf("\na[%d][%d]=%f ",i,j,*(p+i*3+j));
}
输入下标i和j的值后,程序就会输出a[i][j]的值。

这里我们利用了公式p+i*3+j计算出了a[i][j]的首地址。

计算二维数组中任何一个元素地址的一般公式如下:
二维数组首地址+i*二维数组列数+j
请读者分析二维数组的内存分配情况,自行证明上述公式。

上述的指针变量指向的是数组具体的某个元素,因此指针加法的单位是数组元素的长度。

我们也可以定义指向一维数组的指针变量,使它的加法单位是若干个数组元素。

定义这种指针变量的格式为:
数据类型(*变量名称)[一维数组长度];
说明:
(1) 括号一定不能少,否则[]的运算级别高,变量名称和[]先结合,结果就变成了后续章节要讲的指针数组;
(2) 指针加法的内存偏移量单位为:数据类型的字节数*一维数组长度。

例如,下面的语句定义了一个指向long型一维、5个元素数组的指针变量p:
long (*p)[5];
指针变量p的特点在于,加法的单位是4*5个字节,p+1跳过了数组的5个元素。

上述指针变量的特点正好和二维数组的行指针相同,因此我们也可以利用指针变量进行整行的跳动:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0};
float (*p)[3];
inti,j;
printf("Please input i =");
scanf("%d", &i);
printf("Please input j =");
scanf("%d", &j);
p=a;
printf("\na[%d][%d]=%f ",i,j,*(*(p+i)+j));
}
说明:
(1) p定义为一个指向float型、一维、3个元素数组的指针变量p。

(2) 语句p=a将二维数组a的首地址赋给了p。

根据p的定义,p加法的单位是3个float型单元,因此p+i等价于a+i,*(p+i)等价于*(a+i),即a[i][0]元素的地址,也就是该元素的指针。

(3) *(p+i)+j等价于& a[i][0]+j,即数组元素a[i][j]的地址;
(4) *(*(p+i)+j)等价于(*(p+i))[j],即a[i][j]的值。

p在定义时,对应数组的长度应该和a的列长度相同。

否则编译器检查不出错误,但指针偏移量计算出错,导致错误结果。

例8-1 输入一组整数(数量不超过100),统计其中偶数和奇数的总和(用指针方式)。

可以使用一个int型数组保存输入的一组整数,然后利用指针依次遍历数组的元素,统计偶数和奇数的总和。

a[i]和*(a+i)是等价的。

偶数和奇数的辨别可以用整数%2的方法,%2为0则为偶数,否则为奇数。

main()
{
int a[100],i,j,nLen, nEvenCount=0, nOddCount=0,*p=a;
/*输入整数的个数*/
printf("\nPlease input count of integers: ");
scanf("%d",&nLen);
/*依次输入一组整数*/
printf("Please input integers: ");
for(i=0;i<nLen;i++)
scanf("%d",p+i); /*等价于scanf("%d", &a[i])*/
/*统计偶数和奇数的个数*/
for(i=0;i<nLen;i++,p++)
{
if(*p%2==0)
nEvenCount++;
else
nOddCount++;
}
/*打印输出结果*/
printf("There are %d even numbers, %d odd numbers. ",nEvenCount, nOddCount);
}
程序的运行结果为:
Please input count of integers: 6
Please input integers: 1 3 4 65 8 66
There are 3 even numbers, 3 odd numbers.
上面的例子中,指针变量指向了数组a的首地址,p+i即为数组元素a[i]的指针,因此scanf语句中可以使用p+i作为参数。

统计偶数和奇数时,循环每次使p指向数组的下一个元素,循环中*p的值是数组对应元素的值。

注意nEvenCount和nOddCount一定要初始化为零,否则它们都是随机数。

例8-2 编写一个函数,可以实现2*3 3*4矩阵相乘运算(用指针方式)。

使用一个2×3的二维数组和一个3×4的二维数组保存原矩阵的数据;用一个2×4的二维数组保存结果矩阵的数据。

结果矩阵的每个元素都需要进行计算,可以用一个嵌套的循环(外层循环2次,内层循环4次)实现。

根据矩阵的运算规则,上述矩阵每个元素的计算方法为:
因此,内层循环里可以再使用一个循环,累加得到每个元素的值。

一共使用三层嵌套的循环。

main()
{
inti,j,k, a[2][3],b[3][4],c[2][4];
/*输入a[2][3]的内容*/
printf("\nPlease input elements of a[2][3]:\n");
for(i=0;i<2;i++)
for(j=0;j<3;j++)
scanf("%d", a[i]+j); /* a[i]+j等价于&a[i][j]*/
/*输入b[3][4]的内容*/
printf("Please input elements of b[3][4]: \n");
for(i=0;i<3;i++)
for(j=0;j<4;j++)
scanf("%d", *(b+i)+j); /* *(b+i)+j等价于&b[i][j]*/
/*用矩阵运算的公式计算结果*/
for(i=0;i<2;i++)
for(j=0;j<4;j++)
{
*(c[i]+j)=0; /* *(c[i]+j)等价于c[i][j]*/
for(k=0;k<3;k++)
*(c[i]+j)+=a[i][k]*b[k][j];
}
/*输出结果矩阵c[2][4]*/
printf("\nResults: ");
for(i=0;i<2;i++)
{
printf("\n");
for(j=0;j<4;j++)
printf("%d ",*(*(c+i)+j)); /* *(*(c+i)+j)等价于c[i][j]*/
}
}
运行结果为:
Please input elements of a[2][3]:
1 2 3
4 5 6
Please input elements of b[3][4]:
1 2 3 4
5 6 7 8
9 10 11 12
Results:
38 44 50 56
83 98 113 128
上面的例子复习了二维指针的各种表示方法,在实际应用中可以灵活使用。

相关文档
最新文档