開啟章節選單
傳統陣列
極為常見為什麼需要陣列
在程式設計中,我們常常需要儲存大量的資料,而一般變數通常只能儲存單一個數值。如果我們需要儲存多個數值,就需要使用陣列了。你可以把陣列想像成是一個「列表」或「清單」,並且可以根據「第幾項」來存取或修改數值。
舉個例子來說,假設你考了很多次 APCS,希望把每次考試的成績都存在程式裡面,該怎麼儲存呢?按照過去的變數使用方法,也許你會這樣宣告:
int apcs_01 = 3; // 第一次 APCS 考試成績為 3 級 int apcs_02 = 2; // 第二次 APCS 考試成績為 2 級 int apcs_03 = 3; // 第三次 APCS 考試成績為 3 級 int apcs_04 = 5; // 第四次 APCS 考試成績為 5 級
這樣的寫法雖然可以達到目的,但是當考試次數變多時,我們就需要宣告更多的變數,這樣的寫法會讓程式碼變得冗長且難以維護。並且,當我們需要查詢特定的考試成績時,查詢的方式也會變得很麻煩。
int n; cin >> n; if (n == 1) { cout << apcs_01; } else if (n == 2) { cout << apcs_02; } else if (n == 3) { cout << apcs_03; } else if (n == 4) { cout << apcs_04; }
為了解決這個問題,我們需要學習陣列這個資料結構。
陣列的概念
以生活中的例子來說,陣列就像是置物櫃,每個格子都有一個編號,你可以透過編號來存取或修改裡面的物品。
以下圖為例,圖中的「索引值」就是陣列中的「編號」,編號 0 對應的是第一次考試的成績 3 級,編號 1 對應的是第二次考試的成績 2 級,以此類推。
比較值得注意的部分是,在程式中,陣列的索引值是從 0 開始的,這點和一般常見的編號方式稍微不太一樣。
陣列宣告
要宣告一個陣列非常簡單,寫法跟變數很類似。
int apcs[4];
我們只需要在變數名稱後面加上一組中括號 []
,並在中括號中填入陣列的大小即可。舉例來說你只要儲存 4 次 APCS 考試的成績,就需要四格儲存空間,像上面那樣。
但是我們要怎麼決定陣列的大小呢?一般來說,我們會直接開到我們需要的最大值,比如題目說明中有提到考試次數不會超過 100 次,那我們就可以開到 105 的空間,為什麼是 105 呢?那是因為有時候題目操作會容易超過陣列大小一點,所以我們通常會多開一點空間來盡量避免這個問題。
並且,main
函式中的最大陣列限制比較小,所以如果你要開一個很大的陣列(通常是 以上),建議你在 main
函式外宣告。
int apcs[105]; int main() { // 你的程式碼 }
並且,main
函式之外宣告的陣列會有一個特性,就是裡面的數值會被自動初始化為 0,反之,main
函式中宣告的陣列如果沒有初始化,裡面的數值可能會是隨機的,這點要特別注意。
如果需要的話,你也可以使用變數來宣告陣列的大小。
int n; cin >> n; int apcs[n];
但是這樣的寫法在一些編譯器上可能會有問題,所以建議在寫程式時直接填入數值,如果真的需要使用動態大小的陣列,可以使用後面章節提到的 vector。
以上的舉例皆使用 int
類型,然而其他類型也可以開陣列,像是我們可以宣告一個浮點數陣列:
float apcs[105];
而 apcs
這個變數本身其實就是一個指向陣列開頭的指標,所以你可以把它當作一個指標來使用,後面會有更多的說明。
陣列初始化
宣告完陣列,我們還不知道現在陣列中的數值是什麼。就如同路邊的一排房子,你並不知道他們是否曾經住人,以及住過誰;同樣的道理你並不知道陣列宣告的範圍中是否為空,他可能包含之前執行的遺留數值,是一個你不可控的數值。所以我們要透過初始化去覆寫他。
float apcs[4] = {};
透過一個大括號可以初始化一個空值給陣列,每個位置都會是 0。
或許你會看在其他地方看到這樣的寫法:
int apcs[4] = {0};
即便這兩種寫法是等價的,都是將陣列中的所有元素填入 0,但我們認為這種寫法容易誤導初學者。
事實上,上面這個作法是將陣列中的第一個元素填入 0,而其他的元素則會被自動初始化為 0。如果沒搞清楚這個概念的話,有些初學者可能會寫出這樣的東西,並以為可以把整個陣列初始化為 5:
int apcs[4] = {5};
但事實上這樣只會把第一個元素填入 5,其他的元素則會被自動初始化為 0。
因此,我們建議使用 {}
這種寫法,以避免這種誤解。
float apcs[4] = {3, 2, 3, 5};
也可以直接在大括號中填上數值,直接初始數值給陣列。
陣列存取
陣列的存取是透過一個叫「隨機存取」的概念來執行,是一種快速又準確的定位方法。簡單來說,你只需要在陣列名稱後面加上中括號,並在中括號中填入索引值即可取得或修改陣列中的數值。
float apcs[4] = {3, 2, 3, 5}; cout << apcs[3];
在底層的實作中,陣列的資料是連續的,所以我們可以透過索引值來計算出數值的位置。比如說在 int
陣列中,一個 int
型態會占 4 bytes ,如果我們要存取陣列的第 3 個元素,它的位置就會是從陣列開頭往後 的位置。
在這個程式中我們於大括號中填入 3,代表程式存取陣列中索引值 3 中的數值。而陣列的索引值是從 0 開始,也就是在這個例子中索引值是從 0 ~ 3,最後一個數值索引是 3,所以會輸出 5。
再次提醒,陣列索引值是從 0
開始,而不是 1
!
而其他的操作也是一樣的概念:
// 輸入數值給陣列 cin >> apcs[3]; // 更新陣列數值 apcs[2] = 5; // 將數值賦值給其他變數 score = apcs[3]; // 判斷陣列的數值 if (apcs[3] == 5) { cout << "great!" << endl; }
陣列與迴圈
當我們需要對陣列中的所有數值進行操作時,我們通常會使用 迴圈 來達成。透過迴圈,我們可以快速且簡潔地對陣列中的所有數值進行操作。
以下是一個簡單的例子,透過迴圈將陣列中的所有數值輸出:
int apcs[4] = {3, 2, 3, 5}; for (int i = 0; i < 4; i++) { cout << apcs[i] << " "; }
在輸入時,我們可以透過迴圈將數值一一存入陣列。假設我們要輸入四次 APCS 考試的成績,就可以這樣寫:
for (int i = 0; i < 4; i++) { cin >> apcs[i]; }
好用的函式
在 C++ 中,有許多好用的函式可以讓我們更方便地操作陣列。以下是一些常用的函式:
reverse(begin, end)
:將陣列中的元素反轉。max_element(begin, end)
:找出陣列中的最大值。min_element(begin, end)
:找出陣列中的最小值。fill(begin, end, value)
:將陣列中的所有元素填入相同的值。count(begin, end, value)
:計算陣列中某個值出現的次數。find(begin, end, value)
:找出陣列中第一個出現的某個值。copy(begin, end, dest)
:將陣列中的元素複製到另一個陣列。
這些函式都可以在 algorithm
標頭檔中找到,使用時需要加上 #include <algorithm>
,當然如果你已經使用了 bits/stdc++.h
,就不需要再加入這個標頭檔了。
接下來我們會一一介紹這些函式的使用方法。
或許你不知道「函式」是什麼,不過你可以暫時先把它當作是一個「功能」或「語法」,在後面的章節中我們會更詳細地介紹函式這個概念。
找出陣列中的最大值
這個 max_element
函式可以幫助我們找出陣列中的最大值,回傳的是一個 Iterator ,所以我們需要透過 *
來取得數值,並且要傳入兩個參數,分別是陣列的開頭與結尾(型態為 Iterator)。
int apcs[4] = {3, 2, 3, 5}; // 找出陣列中的最大值 int max_score = *max_element(apcs, apcs + 4); cout << max_score << endl;
找出陣列中的最小值
這個 min_element
函式可以幫助我們找出陣列中的最小值,回傳的是一個 Iterator ,所以我們需要透過 *
來取得數值,並且需要傳入兩個參數,分別是陣列的開頭與結尾(型態為 Iterator)。
int apcs[4] = {3, 2, 3, 5}; // 找出陣列中的最小值 int min_score = *min_element(apcs, apcs + 4); cout << min_score << endl;
將陣列中的元素填入相同的值
這個 fill
函式可以幫助我們將陣列中的所有元素填入相同的值,要傳入三個參數,分別是陣列的開頭、結尾以及要填入的值,開頭與結尾的型態都是 Iterator。
int apcs[4] = {3, 2, 3, 5}; // 將陣列中的所有元素填入 0 fill(apcs, apcs + 4, 0); for (int i = 0; i < 4; i++) { cout << apcs[i] << " "; }
將陣列中的元素反轉
reverse
函式可以幫助我們將陣列中的元素反轉,要傳入兩個參數,分別是陣列的開頭與結尾,開頭與結尾的型態都是 Iterator。這個函式不會回傳任何值,會直接修改陣列中的元素。
int apcs[4] = {3, 2, 3, 5}; // 將陣列中的元素反轉 reverse(apcs, apcs + 4); // 輸出反轉後的陣列 for (int i = 0; i < 4; i++) { cout << apcs[i] << " "; }
計算陣列中某個值出現的次數
count
函式可以幫助我們計算陣列中某個值出現的次數,要傳入三個參數,分別是陣列的開頭、結尾以及要計算的值,開頭與結尾的型態都是 Iterator。
int apcs[4] = {3, 2, 3, 5}; // 計算陣列中 3 出現的次數 int count_3 = count(apcs, apcs + 4, 3); cout << count_3 << endl; // 2
找出陣列中第一個出現的某個值
find
函式可以幫助我們找出陣列中第一個出現的某個值,要傳入三個參數,分別是陣列的開頭、結尾以及要找的值,開頭與結尾的型態都是 Iterator。這個函式會回傳一個 Iterator ,所以我們需要透過 *
來取得數值。如果找不到指定的值,則會回傳結尾的 Iterator ,在這個例子中結尾的 Iterator 就是 apcs + 4
。
int apcs[4] = {3, 2, 3, 5}; // 找出陣列中第一個出現的 2 int* it = find(apcs, apcs + 4, 2); if (it != apcs + 4) { cout << *it << endl; // 2 (數值) cout << it - apcs << endl; // 1 (索引值) } else { cout << "Not found" << endl; }
將陣列中的元素複製到另一個陣列
copy
函式可以幫助我們將陣列中的元素複製到另一個陣列,要傳入三個參數,分別是原陣列的開頭、結尾以及目標陣列的開頭,開頭與結尾的型態都是 Iterator。
int apcs[4] = {3, 2, 3, 5}; int copy_apcs[4]; // 將 apcs 中的元素複製到 copy_apcs copy(apcs, apcs + 4, copy_apcs); for (int i = 0; i < 4; i++) { cout << copy_apcs[i] << " "; }
小測驗
陣列索引值是從多少開始?