一維陣列


現在要整理全班的程式設計小考成績,你希望寫個小程式,全班共有 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];
}

直接比較兩個陣列是否相同的話,並不是比較其內容,而是比較兩個陣列變數的位址值,若想比較兩個陣列元素內容是否相同,也要用逐個元素進行比對。