10.1 数组
数组由一系列相同的元素构成。
数组声明中包括数组元素的数目和元素的类型。
一些数组声明的例子:
float candy[365]; /*365个浮点数的数组*/
char code[12]; /*12个字符的数组*/
int states[50]; /*50个整数的数组*/
方括号表示标识符为数组,方括号的数字指明了数组所包含元素数目。
要访问数组中的元素,可以使用下标数字来表示单个元素。下标数字也称索引(index),是从0开始计数的。因此,candy[0]表示candy首元素,candy[364]是第365个元素,也就是最后一个元素。
10.1 初始化
程序中通常使用数组来存储数据。
C为数组初始化引入以下新语法:
int powers[8] = {1,2,4,6,8,16,32,64}; /*只有ANSI C 支持这种初始化方式*/
从上例中可以看出,可以使用花括号括起来的一系列数值来初始化数组。数值之间用逗号隔开在数值和逗号之间可以使用空格符。
程序清单10.1 day_mon1.c程序
#include <stdio.h>
#define MONTHS 12
int main (void)
{
int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
int index;
for(index=0;index<MONTHS;index++)
printf("MONTH %d has %d days.\n",index+1,days[index]);
return 0;
}
注意本例采用标识符常量MONTHS来代表数组的大小。这是一种我们所推荐的做法。如果需要修改数组大小 ,只需要修改#define语句即可,无须查找并修改程序中每一处使用数组大小的地方。
对数组使用const的方法
有时需要使用只读数组,也就是程序从数组中读取数值,但是不向数组中写数据。在这种情况下声明并初始化数组时,建议使用关键字const。我们对程序清单10.1的一部分进行优化,如下:
const int days[MONTHS] = {31,28,......};
使用未经初始化的数组会出现什么情况呢?
与普通变量相似,在初始化之前数组元素的数值是不定的。编译器使用的数值是存储单元中已有的数值。
*************************************************
存储类解释
和其他变量相似,数组 可以被定义为多种存储类(stotage class),第12章将详述此主题。目前,只需要了解本章的数组属于自动存储类。也就是说,数组 是在一个函数内声明的,并且声明时没有使用关键字static。到目前为止,本书所用的变量和数组都是自动类型的。
现在提起存储类的原因是,不同存储类有时具有不同的属性,因此不能把配音的知识推广到其他存储类。例如,如果没有进行初始化,一些存储类的变量和数组会把它们的存储单元设置为0.
************************************************
初始化列表中的元素数目应该和数组大小一致。如果二者不一致,会出现什么情况?
当数值数目少于数组元素数目时,多余的数组元素被初始化为0。也就是说,如果不初始化数组,数组元素和未初始化的变量一样,其中存储的是无用数值;但如果部分初始化数组,未初始化的元素则被设置为0。
如果初始化列表中项目的个数大于数组大小,编译器会毫不留情的认为这是一个错误。然而,可以采用另外一种形式以避免受到编译器的这种奚落:您可以省略括号中的数字,从而让编译器自动匹配数组大小和初始化列表中的项目数目。
程序清单10.4 day_mon2.c程序
#include <stdio.h>
int main (void)
{
int days[] = {31,28,31,30,31,30,31,31,30,31,30};
int index;
for(index=0;index<sizeof days / sizeof days[0];index++)
printf("MONTH %d has %d days.\n",index+1,days[index]);
return 0;
}
程序清单10.4 中有两点需要注意:
1、当使用空的方括号对数组进行初始化时,编译器会根据列表中的数值数目来确定数组大小。
2、注意for循环的控制语句。由于人工计算容易出错,因此可以让计算机来计算数组的大小。运算符sizeof给出其后的对象或类型的大小(以字节为单位)。因此sizeof days是整个数组的大小(以字节为单位),sizeof days[0]是一个元素的大小(以字节为单位)。整个数组的大小除以单个元素的大小就是数组中元素的数目。
3、自动计数的缺点:初始化的元素个数有误时,我们可能意识不到(上例中初始化项目只有10个,应该是12个)。
10.1.2 指定初始化项目
c99增加了一种新特性:指定初始化项目。
此特性允许选择对某些元素进行初始化。在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素:
int arr[6] = {[5]=212}; //把arr[5]初始化为212
对于通常的初始化,在初始化一个或多个元素后, 未经初始化的元素都将被设置为0。
程序清单10.5 designate.c程序
#include <stdio.h>
#define MONTHS 12
int main (void)
{
int days[MONTHS] = {31,28,[4]=31,30,31,[1]=29};
int index;
for(index=0;index<MONTHS;index++)
printf("MONTH %d has %d days.\n",index+1,days[index]);
return 0;
}
输出结果如下:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
9 0
10 0
11 0
12 0
从输出结果可以看出指定初始化项目有两个重要特性。
第一,如果在一个指定初始化项目后跟有不止一个值,例如在序列[4]=31,30,31中这样,则这些数值将用来对后续的数组元素初始化。也就是说,把31赋给days[4]之后,接着把30和31分别赋给days[5]和days[6]。
第二,如果多次对一个元素进行初始化,则最后一次有效。例如,前面把days[1]初始化为28,而后面的指定初始化[1]=29覆盖了前面的值,于是days[1]的数值最终为29。
10.1.3 为数组赋值
声明完数组后,可以借助数组的索引(下标)对数组成员进行赋值。
/*数组赋值*/
#include <stdio.h>
#define SIZE 50
int main (void)
{
int counter,evens[SIZE];
for (counter = 0;counter < SIZE;counter++)
evens[counter]=2*counter;
...
}
注意这种赋值的方式是使用循环对元素逐个赋值。C不支持把数组作为一个整体来进行赋值,也不支持用花括号括起来的列表形式进行赋值(初始化的时候除外):
/*无效的数组赋值*/
#define SIZE 5
int main (void)
{
int oxen[SIZE] = {5,3,2,8}; /*这里是可以的*/
int yaks[SIZE];
yaks = oxen; /*不允许*/
yaks[SIZE]=oxen[SIZE]; /*不正确*/
yaks[SIZE]={5,3,2,8}; /*不起作用*/
10.1.4 数组边界
使用数组的时候,需要注意数组索引不能超过数组边界。也就是说,数组索引应该具有对于数组来说有效的值。
程序清单 10.6 bounds.c程序
//bounds.c --超出数组的边界
#include <stdio.h>
#define SIZE 4
int main (void)
{
int value1=44;
int arr[SIZE];
int value2=88;
int i;
printf("value1 = %d,value = %d\n",value1,value2};
for (i = -1;i<=SIZE;i++)
arr[i] = 2*i+1;
for (i = -1;i<7;i++)
printf("%2d %d\n",i,arr[i]);
printf(value1 = %d,value2 = %d\n",value1,value2);
return 0;
}
程序不检查索引的合法性。
在标准C中,如果使用了错误的索引,程序执行结果是不可知的。也就是,程序也许能够运行,但是运行结果可以很奇怪,也可能会异常中断程序的执行。我们使用Digital Mars8.4运行程序,其输出结果如下:
value1 = 44,value2=88
-1 -1
0 1
1 3
2 5
3 7
4 9
5 5
6 1241520
value1=-1,value2=9
注意,我们使用的编译器看起来是value2正好存储在数组后面的那个存储单元中,把value1存储在数组前面的那个存储单元中(其他编译器可能采取不同的顺序在内存中存储数据)。这样arr[-1]就和value1对应同一存储单元,arr[4]就和value2对应同一存储单元。
因此,使用超出数组边界的索引会改变其他变量的数值。对于不同编译器,输出结果可能不同。
一件需要记住的简单事情就是,数组的计数是从0开始的。避免出现这个问题比较简单的方法是:在数组声明中使用符号常量,然后程序中需要使用数组大小的地方都直接引用符号常量:
#define SIZE 4
int main(void)
{
int arr[SIZE];
for(i=0;i<SIZE;i++)
...
}
这样做的好处是保证整个程序中数组大小始终一致。
10.1.5 指定数组大小
在前面提到的例子中,我们声明数组时使用的是整数常量。
还允许使用什么?直到C99标准出现之前,声明数组时在方括号内只能使用整数常量表达式。整数常量表达式是由整数常量组成的表达式。sizeof表达式被认为是一个整数常量,而一个const值却不是整数常量。并且该表达式的值必须大于0:
int n=5;
int m=8;
float a1[5]; //可以
float a2[5*2+1]; //可以
float a3[sizeof(int)+1]; //可以
float a4[-4]; //不可以,数组大小必须大于0
float a5[0]; //不可以,数组大小必须大于0
float a6[2.5] //不可以,数组大小必须是整数
float a7[(int)2.5]; //可以,把float类型指派为int类型
float a8[n]; //C99之前不可以
float a9[m]; //C99之前不可以
遵循C90标准的C编译器不允许最后两个声明。而c99允许这两个声明,但这创建了一种新数组,称为变长数组(variable length array),简称VLA。
VLA有些限制,例如,声明时不能进行初始化。