Appearance
陣列與字串
前面介紹了不少一般的變數,但如果要規模化儲存,就需要依靠 陣列,其中的字元陣列擁有一些特別的性質,叫做 字串。
陣列
陣列宣告
陣列的使用圍繞著 中括號 [],有兩種主要宣告方式
- 寫明長度:
int arr[10]長度為 10 的陣列 - 寫明內容:
int arr[]={0, 1, 2}長度為 3 的陣列,根據大括號內的內容數量改變
初始化
陣列的初始化在很多場合都很重要。
- 在定義時初始化: 從第 0 號元素開始一個一個寫入值,沒有特別註明的就是 0c
int arr[10] = {1, 2, 3}; for迴圈: 自定義初始化方式cfor (int i = 0; i < 10; i++) { arr[i] = i; //0, 1, 2, 3, ... , 9 }- 特別的初始化方式: 這個方法比較進階也比較少用,從 C99 開始支援c
// 等同 {0, 20, 0, 40} int arr[10] = {[3] = 40, [1] = 20};
陣列索引 (Array Index)
或許你剛才就已經發現了,陣列的元素是從第 0 個開始數的,這種 反人類 設計是有一些歷史因素的,但現在是多數程式語言的標準。
陣列名:這區記憶體的「門牌號碼」
宣告int arr[5]時,arr這個名字在電腦眼裡,代表的就是這 5 個連續格子的第一個格子的位置,也是起點位置。索引
[]:這是「偏移步數」
可以把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) 比對,因為字串名稱代表的是他在記憶體的位置。