03-初始化数组
10.1.1 初始化数组
数组通常被用来存储程序需要的数据。例如,一个内含12个整数元素的数组可以存储12个月的天数。在这种情况下,在程序一开始就初始化数组比较好。下面介绍初始化数组的方法。
只存储单个值的变量有时也称为标量变量(scalar variable),我们已经很熟悉如何初始化这种变量:
int fix = 1;
float flax = PI * 2;
代码中的 PI
已定义为宏。C使用新的语法来初始化数组,如下所示:
int main(void)
{
int powers[8] = {1,2,4,6,8,16,32,64}; /* 从ANSI C开始支持这种初始化 */
...
}
如上所示,用以逗号分隔的值列表(用花括号括起来)来初始化数组,各值之间用逗号分隔。在逗号和值之间可以使用空格。根据上面的初始化,把 1
赋给数组的首元素( powers[0]
),以此类推(不支持ANSI的编译器会把这种形式的初始化识别为语法错误,在数组声明前加上关键字 static
可解决此问题。第12章将详细讨论这个关键字)。
程序清单10.1演示了一个小程序,打印每个月的天数。
程序清单10.1 day_mon1.c
程序
/* 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 %2d has %2d days.\n", index + 1, days[index]);
return 0;
}
该程序的输出如下:
Month 1 has 31 days.
Month 2 has 28 days.
Month 3 has 31 days.
Month 4 has 30 days.
Month 5 has 31 days.
Month 6 has 30 days.
Month 7 has 31 days.
Month 8 has 31 days.
Month 9 has 30 days.
Month 10 has 31 days.
Month 11 has 30 days.
Month 12 has 31 days.
这个程序还不够完善,每4年打错一个月份的天数(即,2月份的天数)。该程序用初始化列表初始化 days[]
,列表(用花括号括起来)中用逗号分隔各值。
注意该例使用了符号常量 MONTHS
表示数组大小,这是我们推荐且常用的做法。例如,如果要采用一年13个月的记法,只需修改 #define
这行代码即可,不用在程序中查找所有使用过数组大小的地方。
注意 使用const声明数组 有时需要把数组设置为只读。这样,程序只能从数组中检索值,不能把新值写入数组。要创建只读数组,应该用
const
声明和初始化数组。因此,程序清单10.1中初始化数组应改成:
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
这样修改后,程序在运行过程中就不能修改该数组中的内容。和普通变量一样,应该使用声明来初始化
const
数据,因为一旦声明为const
,便不能再给它赋值。明确了这一点,就可以在后面的例子中使用const
了。
如果初始化数组失败怎么办?程序清单10.2演示了这种情况。
程序清单10.2 no_data.c
程序
/* no_data.c -- 为初始化数组 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int no_data[SIZE]; /* 未初始化数组 */
int i;
printf("%2s%14s\n", "i", "no_data[i]");
for (i = 0; i < SIZE; i++)
printf("%2d%14d\n", i, no_data[i]);
return 0;
}
该程序的输出如下(系统不同,输出的结果可能不同):
i no_data[i]
0 0
1 4204937
2 4219854
3 2147348480
使用数组前必须先初始化它。与普通变量类似,在使用数组元素之前,必须先给它们赋初值。编译器使用的值是内存相应位置上的现有值,因此,读者运行该程序后的输出会与该示例不同。
注意 存储类别警告 数组和其他变量类似,可以把数组创建成不同的存储类别(storage class)。第12章将介绍存储类别的相关内容,现在只需记住:本章描述的数组属于自动存储类别,意思是这些数组在函数内部声明,且声明时未使用关键字
static
。到目前为止,本书所用的变量和数组都是自动存储类别。 在这里提到存储类别的原因是,不同的存储类别有不同的属性,所以不能把本章的内容推广到其他存储类别。对于一些其他存储类别的变量和数组,如果在声明时未初始化,编译器会自动把它们的值设置为0。
初始化列表中的项数应与数组的大小一致。如果不一致会怎样?我们还是以上一个程序为例,但初始化列表中缺少两个元素,如程序清单10.3所示:
程序清单10.3 somedata.c
程序
/* some_data.c -- 部分初始化数组 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int some_data[SIZE] = { 1492, 1066 };
int i;
printf("%2s%14s\n", "i", "some_data[i]");
for (i = 0; i < SIZE; i++)
printf("%2d%14d\n", i, some_data[i]);
return 0;
}
下面是该程序的输出:
i some_data[i]
0 1492
1 1066
2 0
3 0
如上所示,编译器做得很好。当初始化列表中的值少于数组元素个数时,编译器会把剩余的元素都初始化为 0
。也就是说,如果不初始化数组,数组元素和未初始化的普通变量一样,其中存储的都是垃圾值;但是,如果部分初始化数组,剩余的元素就会被初始化为 0
。
如果初始化列表的项数多于数组元素个数,编译器可没那么仁慈,它会毫不留情地将其视为错误。但是,没必要因此嘲笑编译器。其实,可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数(见程序清单10.4)
程序清单10.4 day_mon2.c
程序
/* day_mon2.c -- 让编译器计算元素个数 */
#include <stdio.h>
int main(void)
{
const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31 };
int index;
for (index = 0; index < sizeof days / sizeof days[0]; index++)
printf("Month %2d has %d days.\n", index + 1, days[index]);
return 0;
}
在程序清单10.4中,要注意以下两点。
- 如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小。
- 注意
for
循环中的测试条件。由于人工计算容易出错,所以让计算机来计算数组的大小。sizeof
运算符给出它的运算对象的大小(以字节为单位)。所以sizeof days
是整个数组的大小(以字节为单位),sizeof days[0]
是数组中一个元素的大小(以字节为单位)。整个数组的大小除以单个元素的大小就是数组元素的个数。
下面是该程序的输出:
Month 1 has 31 days.
Month 2 has 28 days.
Month 3 has 31 days.
Month 4 has 30 days.
Month 5 has 31 days.
Month 6 has 30 days.
Month 7 has 31 days.
Month 8 has 31 days.
Month 9 has 30 days.
Month 10 has 31 days.
我们的本意是防止初始化值的个数超过数组的大小,让程序找出数组大小。我们初始化时用了10个值,结果就只打印了10个值!这就是自动计数的弊端:无法察觉初始化列表中的项数有误。
还有一种初始化数组的方法,但这种方法仅限于初始化字符数组。我们在下一章中介绍。