tags: 大一程設 東華大學 東華大學資管系 基本程式概念 資管經驗分享

陣列續談-上

前言

上一份筆記講到如何為陣列附值,也講到如何宣告一維、二維陣列,而這份筆記將更深入地針對陣列來談一些東西。

一維陣列宣告與儲存

相信有看過前一份筆記都知道陣列要如下宣告:

int score[10];

透過這樣一行我們知道我們宣告了一個陣列名為 score 含有十個儲存空間,每個空間必須都儲存整數,而一個整數在 C++ 佔 4 bytes(通常情況,不考慮 OS 的不同),所以我們知道這個陣列總共被派出了 40 bytes(4 * 10)。

不希望看到的宣告方式

int a = 0; cin >> a; int score[a];

一般來說,一般的陣列會希望程式一跑起來編譯的時候就知道大小為何,然而上面這樣的方式是之後再根據使用者的需求配置空間出來,這件事在一些 compiler 是不被允許的,因為對於記憶體的存取其實規範是有點嚴謹的。

但今天其實真的會有要動態配置的情況,該怎麼辦呢?
下學期教動態陣列或是 vector 你就會知道了!
Orange

陣列們的記憶體位置

相信大家對於宣告一個變數記憶體會派出一個空間來儲存他這件事已經不陌生,那陣列也是一種資源,所以我們宣告陣列勢必也需要記憶體來儲存他。
以上面 array 為例,有十格空間,所以我們其實可以得到 10 個記憶體位置。

還記得我有教過取得一個變數的記憶體空間可以用 & 符號。
Orange

而在 C++ 裡面,我們對一個陣列的陣列頭(第一個位置)有特殊的存取方式。

cout << score << endl; // 這樣寫會印出 score[0] 的記憶體位置 cout << &score[0] << endl; // 這樣也是,因為使用了 & 符號 cout << score[0] << endl; //取得 score[0] 的值 cout << &score[1] << endl; //取得 score[1] 的記憶體位置 cout << score[1] << endl; //取得 score[1] 的值

我們來看張圖吧!


針對一維陣列,假設我們今天有一個長度為 10 的 int array,則電腦會配置資源出來,一維陣列的記憶體是會照順序連續分配的,就像上面這張圖,而 0x 開頭代表記憶體位置用 16 進制表示。
大家可以用 for 迴圈遍歷一下陣列,把他們的記憶體位置印出來看看。

難道還有非連續分配嗎?
有哦,就是俗稱的 LinkedList 鏈結串列
有興趣就自己上網搜尋吧,可能下學期拿來電你們XD
Orange

取得陣列長度

其實在 C++ 裡面實作陣列的方式有很多,有 vector、linkedlist、dynamic array,但我們這邊討論最基本的 array,其他的在以後再談。

//遍歷陣列 for(int i = 0; i < sizeof score / sizeof score[0]; i++){ cout << &score[i] << endl; }

上面這串程式碼可以取得陣列的長度,然後用迴圈來尋訪,應該還記得陣列的索引值(index)要從 0 開始!你可能看不懂,所以我們從下面來說明。

sizeof 關鍵字

sizeof 這個關鍵字可以讓你來檢查變數或者資料型態所佔據的記憶體空間大小,以上面為例,score 總共佔 40 bytes,所以 sizeof score 就是 40,而 score[0] 儲存的是一個整數只佔 4 bytes,所以我們就能得到 40/4 = 10。
這樣我們的迴圈就會從 0 跑到 9,就可以完全遍歷一個 array。

sizeof 也能夠用來檢視物件(class instance)的大小,但這就是後話了~
有興趣就自己上網搜尋吧!
Orange

陣列作為參數傳入函式中

在學函式 function 的時候,有教過 function 可能會有 return_type,會有參數 parameters,但在教的時候參數都是放 int、double、string、char 之類的型態,那今天如果想把一個陣列當作參數丟進去函式裡面,該怎麼做呢?

int compute_sum(int score[], int arr_size){ int sum = 0; for(int i = 0; i < arr_size; i++){ sum += score[i]; } return sum; }

這樣就把一個陣列當作參數丟進 function 裡了,所以你可能可以設計一個程式,用來儲存一個學生的成績,然後用 compute_sum 這個函式幫我們算出他的總分。像下面這樣。

int compute_sum(int score[], int arr_size){ int sum = 0; for(int i = 0; i < arr_size; i++){ sum += score[i]; } return sum; } int main(){ int score[5] = {60,70,80,90,100}; cout << compute_sum(score, sizeof score / sizeof score[0]); //應該會印出 400 return 0; }

你可能會納悶,為什麼在呼叫 compute_sum 的時候,第二個參數不寫 5 就好了,要寫那麼複雜,因為今天如果你不知道陣列的長度的話,你必須靠這樣的方式來取得,今天這個大小 5 是因為是你設定的,你能夠很直接地知道,所以養成用 sizeof 的習慣是很好的。

但其實直接寫 5 也完全沒有關係!

陣列當參數的秘辛

透過上面的例子跟說明,你可能會思考,這樣為什麼還要傳遞一個參數呢? 還要特別花一個記憶體來儲存陣列的長度,我直接在函式裡面使用 sizeof 來計算長度不就好了嗎?
如果你有這樣的想法,代表你心中可能有這樣的程式碼。

int compute_sum(int score[]){ int sum = 0; for(int i = 0; i < sizeof score / sizeof score[0]; i++){ sum += score[i]; } return sum; } int main(){ int score[5] = {60,70,80,90,100}; cout << compute_sum(score); return 0; }

你覺得這樣會印出 400 呢? 還是會印出其他的數?

答案是 130
想不到吧!
Orange

原因是什麼呢? 其實陣列當作參數的時候,他會傳的是陣列頭的記憶體位置,也就是第一個位置 score[0] 的記憶體位置。所以在傳參數的時候傳的並不是陣列,在 C++ 裡面其實沒有辦法傳一個陣列,一定是傳陣列頭的記憶體位置。

為什麼?
No why!!!
Orange

那既然他傳的是記憶體位置,你現在的 sizeof score 就不再是 4 * n bytes,你可以自己在 function 裡面印印看,你的 sizeof score 會印出 8,因為在 C++ 裡面,一個記憶體位置會用 8 bytes 來儲存。

所以上面那個例子,sizeof score / sizeof score[0] 會等於 8 / 4,迴圈只會跑 0 跟 1,所以只會 60 + 70 = 130。

那你可能會問,這樣電腦要怎麼知道我後面 score[1~4] 的數值呢?

還記得前面有提到在程式語言裡面,一般的陣列是連續的儲存,被分配的記憶體是連在一起的,你一定要在電腦裡面找到一個空間,連續且足夠大的來儲存你的陣列,所以你的電腦就能夠從陣列頭的記憶體位置,往後去看下一個人是誰。

但重點來了,既然我們沒辦法在函式內透過 sizeof score / sizeof score[0] 取得陣列長度,所以只好在函式呼叫的時候,由我們把陣列長度當作參數傳進來,讓我們去做運算。

小總結
所以一般的 array,通常是用在需求已知長度的情況,我們直接宣告那個長度的陣列。
Orange