以指针形式访问元素的最根本问题就是指针变量的值是否是我们想要访问的元素的地址。理解并找准了这个地址,指针访问就变得非常清晰。
为了理解方便,首先定义一个二维数组a[3][4]
a[3][4]={{10,11,12,13},
{20,21,22,23},
{30,31,32,33}};
数组名是数组首元素的地址,我们先看看这个首元素如何访问。以指针的方式访问我们就要先找到a[0][0]的地址。这个首元素地址非常特殊,因为它集合了太多的含义。我们结合程序来看。
#include <stdio.h>
int main()
{
int a[3][4] = {{10,11,12,13},
{20,21,22,23},
{30,31,32,33}};
printf("一个int型变量所占的字节:%d字节\n",sizeof(int));
int *p = 0;
printf("指针p的初始地址:%d\n",p);
p = (int*)a;
printf("指针p的赋值之后地址:%d\n",p);
printf("*P的值 :%d\n",*p);
printf("a[0][0]的值:%d\n",a[0][0]);
printf("********************************\n");
printf("二维数组的首元素地址:%d\n",a);
printf("a+1地址 :%d\n",a+1);
printf("*a+1地址:%d\n",*a+1);
printf("********************************\n");
printf("输出a :%d\n",a);
printf("输出&a :%d\n",a);
printf("输出a+1 :%d\n",a+1);
printf("输出&a+1:%d\n",&a+1);
}
数组名a即为数组首元素a[0][0]的地址,将a赋值给指针变量p,则*p
即是a[0][0]的元素值10。但是a其实只是和a[0][0]的地址值相同而已,并不是专门指向a[0][0]的地址。我们简单做个试验就可以知道。如果a指向a[0][0],那么a+1就是a[0][1]的地址,但是输出a+1的值是2293280,数组的首地址是2293264。2293280-2293264=16,每个int型变量在内存中占4个字节,那么16/4=4,所以a其实是指向二维数组每行的首地址。但是要访问数组元素是一定要指向每个元素地址的,所以一定有一个方法使a变成指向某行某列元素的地址。这个方法就是*a
。我们可以看到*a+1
的地址为2293268,二维数组元素的首地址为2293264,两个地址置差为4,也就是一个int型数据所占的内存大小。所以指针访问二维数组元素的各个元素地址就出现了。
总结起来有两条:
1.二维数组名是数组首元素的地址,但是这个地址是行地址。
2.将数组名带星号后,这个地址就指向了某行某列的地址。
为什么要区分指向行地址和某行某列元素的地址,因为在对于指针+1的操作得到的值是完全不同的。就像上面的输出结果,a指向的是行地址,所以a+1得到的是数组首元素地址加上一行元素之后的地址,也就是第二行首元素的地址。但是*a
指向的是第零行第零列的地址,*a+1
的地址就是二维数组第二个元素的地址,也就是a[0][1]的地址。
总结一个规律,*(a+n)+m
就是二维数组中任何一个位置元素的地址值公式。a为二维数组名,n为要输出元素在二维数组第几行,m为要输出元素在二维数组第几列。比如要输出a[2][2]元素,那么它的地址为*(a+2)+2
。这个只是地址,要想得到具体的值只需要在前面整体加星号即可,即*(*(a+2)+2)
就是a[2][2]的值。
还有最后一个问题就是数组名的问题,a和&a有什么区别?&a是合法的么?首先&a是合法的。其次a代表数组第一个元素的首地址,&a代表的是数组变量a的地址。所以a+1和&a+1是两个完全不同的概念。a+1是第二行首元素的地址,&a+1是跨越了整个数组内存的地址。通过程序的输出结果我们也可以印证这一点。2293312-2293264 =48,48/4=12,正好是我们二维数组的元素个数。