这一章我们来讨论一下数组的内涵,对数组的内部构造进行一次解剖,看看里面究竟隐藏了什么秘密。 有了前面两章对数组名和C语言数组本质的澄清,再来理解这一章的内容,就容易多了。
在下面的叙述中,笔者会用到一个运算符sizeof,由于在不同的编译器和编译模式下,对一个地址进行sizeof运算的结果有可能是不同的,为了方便讨论,我都假设地址长度为4个字节。
多数教材在讲述数组的时候,都是把重点放在外部表现形式上,很少涉及数组的内部,只告诉你如何做,却忽视了为什么要这样做。在解释的过程中,还会列出各种各样的表达式,例如:a、a+1、a[0]、a[0][0]、&a[0]、&a[0][0]、*(a+1)等等,让人眼花缭乱。但实际上真正能够用来描述数组内部构造的表达式只有其中的几个。
上一章讲到,C语言的数组实现并非真正的多维数组,而是数组嵌套,访问某个元素的时候,需要逐层向下解析。仍然以上一章的例子数组int a[7][8][9]来说,第一维元素0的值a[0]是a[0]所代表的那个数组的首地址,这个表达式在C语言的数组里面具有特殊的意义,之所以特殊,不仅仅在于它所代表的东西与一般的地址不同,而且类型也并非一般的地址类型,它的类型叫做数组类型,数组类型这个名称在绝大多数教材中是从来没有出现过的,在C89标准中,也仅仅出现在介绍数组定义的那一段。具有数组类型的地址跟一般类型地址的主要区别,在于长度不一样,对一个一般类型的地址进行sizeof运算,结果是4个字节,而a[0]由于代表了一个数组,sizeof(a[0])的结果是整个数组的长度8x9xsizeof(int),并非4个字节。具有数组类型的地址跟数组名一样都是一个符号地址常量,因此它必定是一个右值。数组类型在数组的定义与引用中具有非常重要的作用,它可以用来识别一个标识符或表达式是否真正的数组,一个真正数组的数组名,是一个具有数组类型的符号地址常量,它的长度,是整个数组的长度,并非一般地址的长度,如果一个标识符不具备数组类型,那它就不是一个真正的数组。在后面的章节里,还会再次使用这个概念。
与a[0]类似的数组类型地址还有a[0][0],a[0][0]是a[0]的下一层数组,因此sizeof(a[0][0])的结果是9xsizeof(int)。类似地,对于一个三维数组:
a[i][j][k]
a、a[x]、a[x][y](其中x、y大于等于0而小于i、j)都是具有数组类型的地址常量,而且都是一个右值。这一点要牢牢记住。正是由这些特殊类型的地址构成了整个数组。
以上结论对于n维数组同样适用。
接下来跟各位一起讨论一下跟数组有关的各种表达式的意义及其类型:
&a[0][0][0]:
&a[0][0][0]仅仅是一个地址,它的意义,仅仅表示元素a[0][0][0]的地址,sizeof(&a[0][0][0])的结果是4。不少人把它说成是数组a的首地址,这是错误的,这是对数组首地址概念的滥用。真正能代表数组a的数组首地址只有a本身,a与&a[0][0][0]的意义根本就是两回事,真正的数组首地址是具有数组类型的地址,sizeof(a)结果是ixjxkxsizeof(int),而不是4,只不过由于a[0][0][0]位置特殊,恰好是数组a的第一个元素,所以它们的地址值才相同。而对于a[0]和a[0][0],它们是在数组a内部a[0]和a[0][0]所代表的那个数组的首地址,它们的地址值也是由于位置“特殊”,因此才跟a和&a[0][0][0]一样。这一点一定要区分清楚了。
a+i:
可能有些人会对a+i感到迷惑,数组的首地址加上一个整数是什么呢?它是第一维元素i的地址,sizeof(a+i)为4。
a[i]+j:
跟上面的类似,a[i]+j是a[i]所代表的那个数组的元素j的地址,sizeof(a[i]+j)的结果也为4。
&a:
对数组名取地址在C标准里面是未定义的。这个表达式曾经引起过争论,焦点在于对一个右值取地址的合法性。C89规定&运算符的操作数必须具有具体的内存空间,换言之就是一个左值,但数组名却是一个右值,按照&运算符的要求,这是非法行为。因此,早期的编译器通常规定&a是非法的。但不知道什么原因,现在的编译器都把&a人为地定义成一个比a高一级而地址值跟a一样的地址,但作为比a高一级的地址,有一个行为却非常怪诞,sizeof(&a)的结果跟sizeof(a)相同,这也是人为的痕迹。笔者倾向于把&a定义为非法,应该维护&运算符的权威性,而不是在规定对某个右值取地址为非法的同时,又允许对另一个右值取地址,这是互相矛盾的。
&a[i]和&a[i][j]:
跟&a一样,也是未定义的,同样不符合&运算符的规则。由于a[i]是a[i][j]的上一层数组,有些人可能会想当然地以为:a[i]=&a[i][j],错也,实际上,由于a[i][j]=*(a[i]+j),因此&a[i][j]=&*(a[i]+j),结果是a[i]+j。对于sizeof(&a[i])和sizeof(&a[i][j]),由于是未定义的,因此有些编译器规定其值跟sizeof(a[i])和sizeof(a[i][j])相同,有些编译器却规定为4,就是一个地址的长度。