还谈数组

作者:西邮 王聪

《C专家编程》中曾两次谈到数组,可是我觉得还不够,仍然没有把数组的本质给说出来。这里我想来个了断,终止一切关于数组和指针关系的讨论。:-)

为了先让你困惑一下,先看下面的程序:
[c]

include

typedef int color[3];

size_t array_size(color a)
{
return sizeof(a);
}
size_t array_size_p(color a)
{
return sizeof(
a);
}

int main(void)
{
color c = {0,};
color *p = &c;

    c[1] = 10;
    *(c+1) = 11;
    printf("%dn", c[1]);
    printf("%dn", sizeof(c));
    printf("%dn", array_size(c));
    printf("%dn", array_size_p(p));

    printf("p=%lxn", (unsigned long)p);
    printf("p+1=%lxn", (unsigned long)(p+1));

    return 0;

}
[/c]

如果你看了上面的程序没感到困惑,那说明:要么你完全理解这里的问题;要么你根本就不懂数组和指针的关系,而且根本就没仔细想过,只是知道结果罢了。

如果你去参考《C专家编程》,你得到的是分情况的讨论,一条一条对应得很好,如果你照这样理解的话(就像我以前一样),也感觉不出不舒服来。但是,它没有点破本后的本质,至少我是没看到。我在这里得说一下。这里的问题究竟是什么?问题出在对数组的定义上,理解这里后面的一切都好办了。数组的定义是什么?太简单了,无非就是:

int a[10];

别小看这里,这里太关键了,以至于很多人都给忽略了。这里究竟发生了什么?这里定义了一个长度为10的int数组,其名字叫作a。这似乎是废话,但你琢磨琢磨这里的词,我是把名字和数组分开说的,这就是关键!!进一步说是,a仅仅是个名字,它并不是指向数组第一个元素的整型指针,也不是一个数组类型!更为关键的是,它在内存中根本就不存在!!这样问题就来了,那a怎么还和数组第一个元素的值相同呢?只是碰巧相同罢了!这好比“解放路1号”和“解放路”的地址相同!如果给a下个定义,那它是什么?我觉得,可以这么定义a:它是一个符号,语法上的;如果我们要它的内容,它就代表了其整个数组;如果我们只要它的值,那么它就是数组的起始地址。

我们把上面的定义一段一段解剖来看。“它是一个符号,语法上的”,这可以很好地解释为什么数组名是个常量,它出现的地方都是在语法上进行了适当的替换,至于如何替换,就是下面两条了。

“如果我们要它的内容,它就代表了其整个数组”,这句可以很好地解释下面的例子:
[c]
color *p = &c;
[/c]
这里取址的意义是“我取c代表的整个数组的地址”。这也可以解释sizeof为什么在函数外面作用于数组时取到的是整个数组的大小。所以,说“数组名就是指向其第一个元素的指针”是错误的!

“如果我们只要它的值,那么它就是数组的起始地址”,这句可以很好地解释为什么数组作为函数参数传递时会是指针,因为在函数参数使用其地址明显要比使用其全部内容要快。这也可以解释c[1]存在的道理,因为这里取的也是它代表的地址。所以,说“数组名是数组类型”也是不对的!(如果真是的话,那么它+1和上面例子中的p+1应该是一样的才对。)

综上,数组名什么都是,它是两者的混合:当它表现出内容特性时,它的类型是数组类型;当它表现出地址特性时,它的类型又是其指向第一个元素的指针类型。它也什么都不是,它只是个名字,它在内存中本身是不存在的,存在的是其背后实实在在的数组,它只是它们的一个语法上的代表罢了。这也就决定了从语法上你根本就不可能直接去定义一个数组类型的变量,你只能定义一个指向数组类型的指针,然后间接地去找到背后那个数组类型!

P.S. 因此,在C语言中,你根本就不可能在函数中直接去看到一个数组参数的真实类型,而C++可以,多亏了引用!看下面的例子:
[cpp]

include

using namespace std;
template
size_t array_size(T & ar)
{
return sizeof(ar) / sizeof(ar[0]);
}
int main()
{
int ar[100] = {0};
cout<<array_size(ar)<<endl;

    return 0;

}

[/cpp]

你看,语法上的问题也只能在语法上解决!这叫什么来着?“以其人之道还治其人之身”!

以上纯属一家之言,读后请自己三思!!