您的位置:首页 > 其它

指向多维数组的指针变量

2011-04-11 00:57 405 查看
8.4.3 指向多维数组的指针变量



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;

int i;

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;

int i,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];

int i,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()

{

int i,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

上面的例子复习了二维指针的各种表示方法,在实际应用中可以灵活使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: