一点愚见,与大家交流,从语义的角度来理解这个问题,以及为什么要这么设计:
int A[3]={1,2,3};
物理意义篇
这里的A的物理意义是什么?A是一个数组的名字,代表的是整个数组,证据:sizeof(A)=12;
自然&A代表A的地址,那么&A+1表示什么意思呢?因为A代表的是整个数组,因而&A+1这个地址就在整个数组A的后面,也就是B的首地址,其值就相当于把A的地址值加上整个数组占的字节数(3*4)。实际上&A+1这个地址现在是非法的,那它的作用是什么呢?对了,应该是为二维或更高维的数组服务的。
图(1)B,C定义为A的外部元素,1,2,3定义为A的内部元素。
现在问题来了,上面的办法只能找到A,或者A的外部元素,如何访问A的内部元素呢?
这个时候数组的名字A用上了:
第一个办法:第一个元素可以用A[0],第二一个元素A[1] 。。。。。。
但这个办法还不过隐,汇编一般是用基址+偏移地址的方式访问,因而提出了基址+偏移地址的方式,于是有了第另一种访问方式,(实际上第二种方式可能更早)
第二个办法:那把名字A用作基地址,访问第一个元素可用*(A+0)= *A,第二个元素*(A+1) 。。。。。
综上所述:
这是为了解决不同问题引入的两套机制,一个是为了访问内部元素,一个是为了访问外部元素,而刚好A的地址&A和把名字A作为基地址在数值上是相等的。
本质篇
虽然&A和A的访问方式是为了解决访问外部元素和内部元素提出的不同方法,但本质上都是一样的。
从图(1)可以看出&A和A的运算用共同基地址,实际上&A和A的差别就在于偏移量,&A运算的偏移量是整个数组占的字节数的倍数,而A运算的偏移量是A中一个元素占的字节数的倍数。
在&A中&这个运算符不仅取得了整个数组的基地址,而且还定义了&A=&A+0是偏移量为0倍数组字节数, &A+1的偏移量为1倍的数组字节数,&A+2的偏移量为整个数组字节数的2倍。
同样在可以把A表示成A前面有一个空运算符,这个空运算符不仅获得了A的基地址,而且定义了 [空运算符]A+1的偏移量为一个元素的字节数的1倍。
这些运算符不仅要获得基地址,更重要的是他们规定了偏移量。
不同偏移量的运算符都是首先要获得基地址,而基地址是一样。
运用上面这一条我们尝试来理解二维数组:
int A[3][3];
printf("%p\n", A); // 1
printf("%p\n", &A); // 2
printf("%p\n", &A [0]); // 3
printf("%p\n", A [0]); // 4
printf("%p\n", &A[0][0] ); // 5
printf("%p\n", &A + 1); // 6
printf("%p\n", A+1); // 7
(1) A的物理意义代表的是整个二维数组,那么很自然第6条语句表示就是指向下一个3*3的二维数组的首地址
(2) 第1条语句表示相当对A做空运算,这条表示对A取地址,偏移量为0倍 元素占的字节数,也就是基地址。这里说一下元素的概念:A是二维数组,所以A的元素就是一维数组,这个可以通过第7条语句验证。同理三维数组的元素是二维数组。
(3) 第2语句条表示相当对A做&运算,这条表示对A取地址,偏移量为0倍 二维数组占的字节数,也就是基地址。
(4) 在这里A[0]是一个一维数组,语句3,4可以用前面一维数组的方式来理解即可。
(5) 第5条语句说的是一个很直观的意思,首地址和第一个元素的地址是一样的。现在要思考的是,C语言的数组下表为什么从0开始,而不是从1开始?其原因就是她采用的是用偏移量来访问数组,那么数组第一个元素的偏移量自然就是0.这一点在《c专家编程》里有提到。
扩展篇
有一个问题不能回避就是数组名与指针的关系,数组名什么时候需要转化成指针?
当数组作为一个参数传递给函数时,假设不将其转化成指针会发生什么情况?
参数是靠栈传递,而栈的大小是受限制的,数组数据量大的情况下这就传不了,所以需要将数组名退化成指针。而退化过程就是通过数组名获得基地址。