- C语言中只有一维数组,而且数组的大小必须在编译期就作为一 个常数确定下来。不过,C语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要“仿真"出一个多维数组就不是一件难事。
- 对于一个数组,我们只能做两件事:确定该数组的大小以及获得指向该数组下标为0的指针元素。其他有关数组的操作,实际上都是通过指针进行的。
1. 数组名
在C语言中,在几乎所有使用数组名的表达式中,数组名的值是个指针常量,也就是数组第1个元素的地址。它的类型取决于数组元素的类型:如果它们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型就是“指向其他类型的常量指针”。
请不要根据这个事实得出“数组和指针是相同的”结论。数组具有一些和指针完全不同的特征。例如,数组具有确定数量的元素,而指针只是一个标量值。编译器用数组名来记住这些属性。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。
注意,这个值是指针常量,而不是指针变量。常量的值是不能修改的。只要稍微回想一下,就会认为这个限制是合理的:指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的操作就是把整个数组移动到内存的其他位置。但是,在程序完成链接之后,内存中数组的位置是固定的,所以当程序运行时,再想移动数组就为时已晚了。因此,数组名的值是一个指针常量。
只有在两种场合下,数组名并不用指针常量来表示——当数组名作为sizeof
操作符或单且操作符&的操作数时。sizeof
返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是-个指向数组的指针,而不是一个指向某个指针常量值的指针。
不能用赋值符直接把一个数组的所有元素复制给另一个数组,而是必须使用一个循环,每次复制一个元素。
2.为什么在C语言不检查下标的正确性
标准并未提出这项要求。最早的C编译器并不检查下标,而最新的编译器依然不对它进行检查。这项任务之所以很困难,是因为
下标引用可以作用于任意的指针,而不仅仅是数组名。作用于指针的下标引用的有效性既依赖于该指针当时怡好指向什么内容,也依赖于下标的值。
结果,C的下标检查所涉及的开销比刚开始想象的要多。编译器必须在程序中插入指令,证实下标表达式的结果所引用的元素和指针表达式所指向的元素属于同一个数组。 这个比较操作需要程序中所有数组的位置和长度方面的信息,这将占用一些空间。 当程序运行时,这些信息必须进行更新,以反映自动和动态分配的数组,这又将占用.一定的时间。因此,即使是那些提供了下标检查的编译器通常也会提供一个开关,允许去掉下标检查。
3. 2[array]
将这个表达式转换为对等的间接访问表达式,就会发现它的有效性:
*( 2 + ( array ) )
它和下面的表达式是完全一样的:
*( array + 2 )
也就是array[2]。
这个表达式会大大影响程序的可读性,最好不要使用。
4. 为什么函数原型中的一维数组形参无需写明它的元素类型
因为函数并不为数组分配内存空间形参只是一个指针,它指向的是已经在其他地方分配好了的内存空间。这个事实解释了为什么数组形参可以与任何长度的数组相匹配——它实际上传递的只是数组第一个元素的指针。
另一方面,这种实现使函数无法知道数组的长度。如果函数需要知道数组的长度,它必须作为一个显示的参数传递给函数。
5. 字符数组的初始化
char m[]={
'H','e','l','l','o',0};
语言标准提供了一种用于初始化字符数组的快速方法
char m[]="Hello";
尽管它看上去像是一个字符串常量,实际上并不是。它只是前例的初始化列表的另一种写法。如果它们看上去完全相同,该如何分辨字符串常量和这种初始化列表快速记法呢?它们是根据所处的上下文环境进行区分的。当用于初始化一个字符数组时,它就是一个初始化列表。在其他任何地方,它都表示一个字符串常量。
这里有一个例子:
char message1[] = "Hello";
char *message2 = "Hello";
这两个初始化看上去很像,但它们具有不同的含义。前者初始化个字符数组的元素, 而后者则是一个真正的字符串常量。这个指针变量被初始化为指向这个字符串常量的存储位置。
6. 多维数组
6.1 多维数组的下标引用
int a[3][10];
对表达式:
*( a + 1 ) + 5