現在要整理全班的程式設計小考成績了,現在希望寫個小程式,全班共有 40 名學生,必須有 40 個變數來儲存學生的成績,現在問題來了,根據之前學過的,難道要宣告 40 個名稱不同的變數來儲存學生成績嗎?
不會這麼麻煩的,C++ 提供陣列(array),可以宣告一個以索引(index)作為識別的資料結構,宣告陣列的方式如下:
資料型態 名稱[長度];
長度必須是個編譯時期常數,以下是幾個宣告的範例:
int number[10]; // 宣告 10 個元素的整數陣列
double score[10]; // 宣告 10 個元素的浮點數陣列
char ascii[10]; // 宣告 10 個元素的字元陣列
若要動態宣告陣列長度,可以使用一些資料結構與動態記憶體宣告來解決,這在之後才會說明。
宣告陣列之後,陣列的元素值是未初始的,若想在宣告時初始陣列全部的元素值,可以如下:
int number[10] = {0};
double score[10] = {0.0};
char ascii[10] = {'\0'};
bool flag[10] = {false};
上面的幾個宣告,整數陣列中的元素都會被初始為 0,浮點數陣列則會被初始為 0.0,字元陣列會被初始為空字元('\0'
),而 bool
陣列會被初始為 false
。
也可以在宣告陣列時初始所有的陣列值,例如:
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'};
bool flag[5] = {false, true, false, true, false};
要存取陣列中的元素值時,可以使用下標(Subscript)運算子 []
加上索引」,索引值由 0 開始,下面這個簡單的程式是個示範:
#include <iostream>
using namespace std;
int main() {
constexpr int LEN = 10;
int number[LEN] = {0};
for(int i = 0; i < LEN; i++) {
cout << number[i] << " ";
}
cout << endl;
for(int i = 0; i < LEN; i++) {
number[i] = i;
}
for(int i = 0; i < LEN; i++) {
cout << number[i] << " ";
}
cout << endl;
return 0;
}
執行結果如下:
0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9
陣列在使用時,得知陣列長度是必要的,不可以存取超過陣列長度的記憶體,這會發生無法預期的結果,陣列本身並不知道自己的長度資訊,在上面的範例中,使用了 LEN
來記錄長度,不過,有沒有辦法計算出長度呢?可以使用底下的方式:
#include <iostream>
using namespace std;
int main() {
int number[5] = {0, 1, 2, 3, 4};
int length = sizeof(number) / sizeof(number[0]);
for(int i = 0; i < length; i++) {
cout << number[i] << " ";
}
cout << endl;
return 0;
}
陣列索引值由 0 開始不是沒有原因的,陣列名稱儲存了陣列記憶體的首個位置的位址,而索引值表示陣列元素是相對於陣列首個記憶體位址的位移量(offset),位移的量與資料型態長度有關,如果是 int
整數,每次位移時是一個 int
整數的長度,例如在上例中 number[0]
索引值為 0 時,表示位移量為 0,自然就是指第一個元素,而 number[9]
就是指相對於首個元素的位移量為 9。
C++ 17 的 iterator
提供了 size
函式,可以用來計算陣列大小,不過目前撰寫文件時使用的 g++ 編譯器需要加上 -std=c++17
才可以使用。
在 C++ 11 提供了 begin
與 end
函式,begin
會傳回陣列首個元素的位址,end
傳回最後一個元素下個位置的位址,當對位址值進行運算時,會以資料型態的長度偏移,因此能有以下循序走訪陣列的方式:
#include <iostream>
using namespace std;
int main() {
int number[5] = {0, 1, 2, 3, 4};
for(auto offset = begin(number); offset != end(number); offset++) {
auto n = *offset;
cout << n << " ";
}
cout << endl;
return 0;
}
offset
是個指標(pointer),型態會是 int*
,儲存的是位址,而 *offset
是取得儲存於該位址的值,因為之後才會談到指標,這邊就先用 auto
讓編譯器推斷型態,基於以上的原理,在 C++ 11 提供了 for range 語法,可用於循序走訪陣列的任務:
#include <iostream>
using namespace std;
int main() {
int number[5] = {0, 1, 2, 3, 4};
for(auto n : number) {
cout << n << " ";
}
cout << endl;
return 0;
}
若在宣告陣列時指定各個索引處的的值,可以不用宣告陣列元素大小,例如:
int number[] = {1, 2, 3};
double weight[] = {0.4, 3.2, 1.0, 4.2};
char ch[] = {'A', 'B'};
上面宣告中,number[]
的元素個數會是 3,weight[]
的個數會是 4,而 chs[]
的個數會是 2。
如果使用 const
或 constexpr
來修飾陣列,每個索引位置就成為唯讀。例如:
constexpr int number[] = {1, 2, 3};
number[1] = 10; // error: assignment of read-only location 'number[1]'
不可以將陣列直接指定給另一陣列,例如:
int arr1[5];
int arr2[5];
...
arr1 = arr2; // 錯誤!不能直接指定陣列給另一個陣列
若要將陣列指定給另一個陣列,只能循序逐個元素進行複製,例如:
constexpr int LENGTH = 5;
int arr1[LENGTH];
int arr2[LENGTH];
...
for(int i = 0; i < LENGTH; i++) {
arr1[i] = arr2[i];
}
直接比較兩個陣列是否相同的話,並不是比較其內容,而是比較兩個陣列變數的位址值,若想比較兩個陣列元素內容是否相同,也要用逐個元素進行比對。
如果打算對陣列進行排序、尋找、反轉等操作,可以使用包含 algorithm
標頭檔:
#include <algorithm>
例如下面這個程式直接示範了排序、尋找、反轉等操作:
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
int number[] = {30, 12, 55, 31, 98, 11};
// 排序
sort(begin(number), end(number));
for(auto n : number) {
cout << n << " ";
}
cout << endl;
cout << "輸入搜尋值:";
int search = 0;
cin >> search;
int* addr = find(begin(number), end(number), search);
cout << (addr != end(number) ? "找到" : "沒有")
<< "搜尋值"
<< endl;
// 反轉
reverse(begin(number), end(number));
for(auto n : number) {
cout << n << " ";
}
cout << endl;
return 0;
}
執行結果:
11 12 30 31 55 98
輸入搜尋值:30
找到搜尋值
98 55 31 30 12 11
sort
、find
等函式,也可以作用在 array
、vector
等,實際上,這些函式搭配函式的傳遞會更有效用,這之後都會談到。