Skip to content

陣列與字串

前面介紹了不少一般的變數,但如果要規模化儲存,就需要依靠 陣列,其中的字元陣列擁有一些特別的性質,叫做 字串

陣列

陣列宣告

陣列的使用圍繞著 中括號 [],有兩種主要宣告方式

  • 寫明長度: int arr[10] 長度為 10 的陣列
  • 寫明內容: int arr[]={0, 1, 2} 長度為 3 的陣列,根據大括號內的內容數量改變

初始化

陣列的初始化在很多場合都很重要。

  1. 在定義時初始化: 從第 0 號元素開始一個一個寫入值,沒有特別註明的就是 0
    c
    int arr[10] = {1, 2, 3};
  2. for 迴圈: 自定義初始化方式
    c
    for (int i = 0; i < 10; i++) {
        arr[i] = i; //0, 1, 2, 3, ... , 9
    }
  3. 特別的初始化方式: 這個方法比較進階也比較少用,從 C99 開始支援
    c
    // 等同 {0, 20, 0, 40}
    int arr[10] = {[3] = 40, [1] = 20};

陣列索引 (Array Index)

或許你剛才就已經發現了,陣列的元素是從第 0 個開始數的,這種 反人類 設計是有一些歷史因素的,但現在是多數程式語言的標準。

  1. 陣列名:這區記憶體的「門牌號碼」
    宣告 int arr[5] 時,arr 這個名字在電腦眼裡,代表的就是這 5 個連續格子的第一個格子的位置,也是起點位置。

  2. 索引 []:這是「偏移步數」
    可以把 arr[i]i 理解為:「從起點開始,往後走幾步?」

    • arr[0]:從起點走 0 步 就是起點本人(第一個元素)。
    • arr[1]:從起點走 1 步 到達第二個元素。
    • arr[n]:從起點走 n 步。
歷史因素:為什麼說索引是「偏移量」?

在電腦底層,arr[i] 實際上被換算成: *(arr + i) 這代表「從 arr 這個地址開始,往後跳 i 個整數的大小,然後取出那裡的值」。

如果你從 1 開始數,電腦每次都要做 (i - 1) 的減法運算。對於早期每一秒運算都很珍貴的電腦來說,直接從 0 開始是最省力的做法。這也是為什麼 C 語言能保持極速的原因之一。

使用索引更改數據

在初始化後,陣列能且只能以索引來逐個修改數據。

c
int arr[3] = {1, 2, 3};
arr = {0, 1, 2}; // 不能夠直接寫整個陣列
for (int i = 0; i < 3; i++) arr[i] = i; // 標準做法

多維陣列

如果說前面的一維陣列只是一排座位,那二維陣列就是一個教室。在 C 中,維度可以很高 (我不確定具體限制),但請不要折磨自己。

c
int classroom[3][5] = {
    {0, 1, 2, 3, 4},
    {5, 6, 7, 8, 9},
}; // 3 排座位,每排 5 個人
進階: 偏移量細節

前面有說 classroom 是起點位置 (classroom[0][0]),但在記憶體上這些位置是相聯的一排,不是二維的型態,這時候第二個元素 (偏移量為一) 代表的是 classroom[0][1],如果是第二排的首個元素 (classroom[1][0]) 偏移量就是 5 (一排的長度)。

我當初的考試內容中就有給兩個陣列位置與記憶體位置,求其他陣列元素所在的位置,需要考慮元素大小,指標位置差異等。

陣列作為參數

這是陣列最特殊的地方。當你把陣列傳進函數時,C 語言不會複製整個陣列(那太慢了),它只會把陣列的起點地址傳進去。

c
void func(int arr[]);
int arr[10];
func(arr);

對於二維陣列或更高維陣列,只有最前面的索引可以是空的

c
void func(int arr[][10][10]);
int arr[10][10][10];
func(arr);

由於傳遞的是陣列名稱,也就是記憶體位置,在函數中修改陣列的數值會影響到原本陣列。

在 C 語言實戰中,傳遞陣列時通常會額外傳一個長度參數,否則函數不知道什麼時候該停下來,這就是為什麼很多 C 函數長這樣: void process_data(int arr[], int size);

字串 (String)

字串在 C 語言中其實就是「字元陣列」,通常使用""包住 (char 使用 '')。但它有一個非常重要的約定:必須以 \0 (Null Character) 結尾。

c
char name[] = "hello";
// 6 個字元,最後藏了一個 \0

輸出字串

在前面章節有介紹輸出 string 使用的是 %s,這邊需要傳遞的是陣列名稱,如同在傳遞陣列參數一般。

c
char str[10];
printf("%s", str);
printf 怎麼知道哪裡結束?

當你把 str 交給 printf 時,它只拿到了「起點地址」。它會像個掃描器一樣,從起點開始一個一個字元印出來,直到撞見 \0 為止。 如果你手動把 \0 刪掉,printf 就會失控,繼續印出記憶體裡後方的垃圾數據,直到發生 Segfault(程式崩潰)或撞到另一個 \0

輸入

與其他 scanf 傳遞的參數布一樣,由於陣列名稱本身就是記憶體位址,傳遞陣列名稱可以讓函數更改原本陣列內容,因此不需要額外加上 &。 如果這個字串太短讀不完內容,剩下一部份內容就會留在緩衝區下次 scanf 時讀取。

c
char str[10];
scanf("%s", str);

字串函數

在 C 的 string.h library 中,有很多方便的字串函數可以使用,如分割、尋找、長度等功能,如果有考試需求,請多看看這個函數庫的 functions。

特別提醒,請不要使用 == 比對兩個字串,請使用 strcmp(str1, str2) 比對,因為字串名稱代表的是他在記憶體的位置。