再论C语言数组
C语言处理数组的方式是它广受欢迎的原因之一。C语言对数组的处理是非常有效的,其原因有以下三点:
第一,除少数翻译器出于谨慎会作一些繁琐的规定外,C语言的数组下标是在一个很低的层次上处理的。但这个优点也有一个反作用,即在程序运行时你无法知道一个数组到底有多大,或者一个数组下标是否有效。ANSI/ISOC标准没有对使用越界下标的行为作出定义,因此,一个越界下标有可能导致这样几种后果:
(1) 程序仍能正确运行;
(2) 程序会异常终止或崩溃;
(3) 程序能继续运行,但无法得出正确的结果;
(4) 其它情况。
换句话说,你不知道程序此后会做出什么反应,这会带来很大的麻烦。有些人就是抓住这一点来批评C语言的,认为C语言只不过是一种高级的汇编语言。然而,尽管C程序出错时的表现有些可怕,但谁也不能否认一个经过仔细编写和调试的C程序运行起来是非常快的。
第二,数组和指针能非常和谐地在一起工作。当数组出现在一个表达式中时,它和指向数组中第一个元素的指针是等价的,因此数组和指针几乎可以互换使用。此外,使用指针要比使用数组下标快两倍。
第三,将数组作为参数传递给函数和将指向数组中第一个元素的指针传递给函数是完全等价的。将数组作为参数传递给函数时可以采用值传递和地址传递两种方式,前者需要完整地拷贝初始数组,但比较安全;后者的速度要快得多,但编写程序时要多加小心。C++和ANSIC中都有const关键字,利用它可以使地址传递方式和值传递方式一样安全。
数组和指针之间的这种联系会引起一些混乱,例如以下两种定义是完全相同的:
void f(chara[MAX])
{
/*... */
}
void f(char *a)
{ ·
/*... */
}
注意:MAX是一个编译时可知的值,例如用#define预处理指令定义的值。
这种情况正是前文中提到的第三个优点,也是大多数C程序员所熟知的。这也是唯一一种数组和指针完全相同的情况,在其它情况下,数组和指针并不完全相同。例如,当作如下定义 (可以出现在函数说明以外的任何地方)时:
char a[MAX];
系统将分配MAX个字符的内存空间。当作如下说明时:
char *a;
系统将分配一个字符指针所需的内存空间,可能只能容纳2个或4个字符。如果你在源文件中作如下定义:
char a[MAX];
但在头文件作如下说明;
extern char *a;
就会导致可怕的后果。为了避免出现这种情况,最好的办法是保证上述说明和定义的一致性,例如,如果在源文件中作如下定义:
char a[MAX];
那么在相应的头文件中就作如下说明,
externchar a[];
上述说明告诉头文件a是一个数组,不是一个指针,但它并不指示数组a中有多少个元素,这样说明的类型称为不完整类型。在程序中适当地说明一些不完整类型是很常见的,也是一种很好的编程习惯。
第一,除少数翻译器出于谨慎会作一些繁琐的规定外,C语言的数组下标是在一个很低的层次上处理的。但这个优点也有一个反作用,即在程序运行时你无法知道一个数组到底有多大,或者一个数组下标是否有效。ANSI/ISOC标准没有对使用越界下标的行为作出定义,因此,一个越界下标有可能导致这样几种后果:
(1) 程序仍能正确运行;
(2) 程序会异常终止或崩溃;
(3) 程序能继续运行,但无法得出正确的结果;
(4) 其它情况。
换句话说,你不知道程序此后会做出什么反应,这会带来很大的麻烦。有些人就是抓住这一点来批评C语言的,认为C语言只不过是一种高级的汇编语言。然而,尽管C程序出错时的表现有些可怕,但谁也不能否认一个经过仔细编写和调试的C程序运行起来是非常快的。
第二,数组和指针能非常和谐地在一起工作。当数组出现在一个表达式中时,它和指向数组中第一个元素的指针是等价的,因此数组和指针几乎可以互换使用。此外,使用指针要比使用数组下标快两倍。
第三,将数组作为参数传递给函数和将指向数组中第一个元素的指针传递给函数是完全等价的。将数组作为参数传递给函数时可以采用值传递和地址传递两种方式,前者需要完整地拷贝初始数组,但比较安全;后者的速度要快得多,但编写程序时要多加小心。C++和ANSIC中都有const关键字,利用它可以使地址传递方式和值传递方式一样安全。
数组和指针之间的这种联系会引起一些混乱,例如以下两种定义是完全相同的:
void f(chara[MAX])
{
/*... */
}
void f(char *a)
{ ·
/*... */
}
注意:MAX是一个编译时可知的值,例如用#define预处理指令定义的值。
这种情况正是前文中提到的第三个优点,也是大多数C程序员所熟知的。这也是唯一一种数组和指针完全相同的情况,在其它情况下,数组和指针并不完全相同。例如,当作如下定义 (可以出现在函数说明以外的任何地方)时:
char a[MAX];
系统将分配MAX个字符的内存空间。当作如下说明时:
char *a;
系统将分配一个字符指针所需的内存空间,可能只能容纳2个或4个字符。如果你在源文件中作如下定义:
char a[MAX];
但在头文件作如下说明;
extern char *a;
就会导致可怕的后果。为了避免出现这种情况,最好的办法是保证上述说明和定义的一致性,例如,如果在源文件中作如下定义:
char a[MAX];
那么在相应的头文件中就作如下说明,
externchar a[];
上述说明告诉头文件a是一个数组,不是一个指针,但它并不指示数组a中有多少个元素,这样说明的类型称为不完整类型。在程序中适当地说明一些不完整类型是很常见的,也是一种很好的编程习惯。