数组


现在要整理全班的程序设计小考成绩,你希望写个小程序,全班共有 40 名学生,所以必须有 40 个变量来存储学生的成绩,现在问题来了,根据之前学过的, 难道要定义 40 个名称不同的变量来存储学生的成绩数据吗?

当然不会这么麻烦的,C 提供数组(Array),可以定义一个以索引(index)作为识别的数据结构,定义数组的方式如下:

数据类型 名称[大小];

数据类型可以是intfloatchar等,以下是几个定义的范例:

int number[10];    // 定义 10 个元素的整数数组
double score[10];  // 定义 10 个元素的浮点数数组
char ascii[10];    // 定义 10 个元素的字符数组

这是静态数组的定义方式,事先决定了数组长度,在 C99 前,不可以使用变量来事后决定数组的长度,如果要动态定义数组长度,可以使用一些数据结构与动态内存定义来解决数组大小必须固定的问题,这可以参考〈malloc、free、calloc 与 realloc〉。

C99 加入了可变长度的数组类型(variable length array type),可以使用变量来指定数组长度,而变量值实际上可以是执行时期运算值,在支持的编译器下,以下代码是可行的:

int len = 0;
scanf("%d", &len);
int arr[len];

不过 C11 却将这个功能定为非必要功能,编译器实现不一定得支持,然而大多数编译器都支持这个功能,如果使用的编译器真的不支持,那就得回归动态配置内存的方式。

定义数组之后,数组所配置到的内存空间中所存储的数是未知的,所以在初始化数组元素值之前,当中的元素值是未知的,如果在定义变量时尚未决定数组中的值,可以这么定义数组:

int number[10] = {0};
double score[10] = {0.0};
char ascii[10] = {'\0'};

上面的几个定义,整数数组中的元素都会被初始化为 0,浮点数数组则会被初始化为 0.0,字符数组则会被初始化为空字符('\0'),在定义数组时初始化数组元素,可以避免程序访问到非预期的数值。

也可以在定义数组时初始化所有的数组值,例如:

int number[5] = {0, 1, 2, 3, 4};
double score[5] = {87.0, 78.0, 99.5, 69.5, 82.5};
char ascii[5] = {'A', 'B', 'C', 'D', 'E'};

当要访问数组中的值时,可以使用下标(Subscript)运算符[]加上索引(Index),指定要访问的数组元素,C 的索引值一律由 0 开始,而不是由 1 开始,这点初学者必须特别注意,下面这个简单的程序示范如何使用索引访问数组元素:

#include <stdio.h>
#define LENGTH 10

int main(void) {
    int arr[LENGTH] = {0};
    int i;

    for(i = 0; i < LENGTH; i++) {
        printf("%d ", arr[i]);
    }
    putchar('\n');

    for(i = 0; i < LENGTH; i++) {
        arr[i] = i;
    }

    for(i = 0; i < LENGTH; i++) {
        printf("%d ", arr[i]);
    }
    putchar('\n');

    return 0;
}

执行结果:

0 0 0 0 0 0 0 0 0 0 
0 1 2 3 4 5 6 7 8 9

数组在使用时,得知数组长度是必要的,不可以访问超过数组长度的内存,这会发生无法预期的结果,数组本身并不知道自己的长度信息,在上面的范例中,#define是前置处理器指令,#define LEN 10定义了,当编译器看到LEN时,都自动取代为 10,之后再进行编译。

不过,有没有办法计算出长度呢?可以使用底下的方式:

#include <stdio.h>

int main(void) {
    int number[5] = {0, 1, 2, 3, 4};
    int length = sizeof(number) / sizeof(number[0]);

    for(int i = 0; i < length; i++) {
        printf("%d ", number[i]); 
    }
    printf("\n");

    return 0;
}

数组索引值由 0 开始不是没有原因的,事实上数组名称就指向数组内存的第一个位置的地址,而索引值表示所指定的数组元素,相对于数组第一个内存位置的位移量(Offset)。

位移的量与数据类型长度有关,如果是int整数,则每次位移时是一个int整数的长度,例如在上例中arr[0]索引值为 0,所以表示位移量为 0,自然就是指第一个元素,而arr[9]就是指相对于第一个元素的位移量为 9,C 就是根据数组第一个元素的内存位置与 位移量来得到所指定要访问的数组元素。

如果在定义数组时只希望初始化几个元素,则可以这么定义:

int number[5] = {98, 76}; 
double weight[5] = {0.0, 0.1}; 
char ch[5] = {'A', 'B'};

像上例中,都只初始化索引 0 与索引 1 的两个元素,其他未初始化的元素,整数的话会自动初始化为 0,浮点数的话会自动初始化为 0.0,字符的话会自动初始化为空字符 ,其他未初始化的元素值则是未知的。

若在定义数组时指定各个索引处的的值,可以不用定义数组元素大小,例如:

int number[] = {1, 2, 3};
double weight[] = {0.4, 3.2, 1.0, 4.2};
char ch[] = {'A', 'B'};

上面定义中,number的长度会是 3,weight的长度会是 4,而ch的长度会是 2。

如果使用const来修饰数组,每个索引位置就成为维读。例如:

const int number[] = {1, 2, 3};
number[1] = 10; // error: assignment of read-only location 'number[1]'

不可以将数组直接指定给另一个数组:

int arr1[5];
int arr2[5];
...
arr1 = arr2; // 错误!不能直接指定数组给另一个数组

若要将数组指定给另一个数组,只能顺序逐个元素进行复制,例如:

int arr1[LEN];
int arr2[LEN];
...
for(int i = 0; i < LEN; i++) {
    arr1[i] = arr2[i];
}

直接比较两个数组是否相同的话,并不是比较其内容,而是比较两个数组变量的地址值,若想比较两个数组元素内容是否相同,也要用逐个元素进行比对。


展开阅读全文