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

陣列續談-下

二維陣列

一維的陣列其實非常簡單,但他能夠處理的事情很有限,二維,三維的陣列能夠有效地幫我們處理非常多的問題。

二維陣列的宣告

int scores[30][5];

還記得在上一份筆記我們有舉個例子,如果有想要紀錄一個班級全部學生的五科成績這樣的情況就非常適合用二維陣列。
假設一個班有 30 位同學,就需要 150 格來儲存。當然你可以這樣寫。

int array[150]; for(int i = 0; i < 150; i++){ cout << array[i] << endl; }

這樣寫雖然邏輯沒錯,但在直觀上不好理解,下面這樣的寫法會更好。

for(int i = 0; i < 30; i++){ cout << "the grades of " << i << "th student\n"; for(int j = 0; j < 5; j++){ cout << score[i][j] << endl; } }

這樣的寫法就會印出第一位同學的五科成績(假設國、英、數、自、社),然後再下一次到外層迴圈就會印出第二位、接著下一次外層迴圈變第三位依此類推。

程式碼是一行一行往下執行這個思維不可以不見,千萬別混淆。
一定是外層迴圈再跑內層,內層都跑完再跑下一次的外層!!!
Orange

二維陣列的索引值

上面的內容都了解的話,來看張圖吧! 假設今天有一個 4x3 的陣列。

int grade[4][3]; //4 為 row,3 為 column

請記住直行(column)橫列(row)
Orange

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

所以你應該可以了解了,陣列宣告的第一個中括弧是橫向,第二個中括弧是直向。

row-major of array

由橫向優先遍歷陣列。

for(int i = 0; i < 4; i++) for(int j = 0; j < 3; j++) cout << grade[i][j] << endl;

column-major of array

由縱向(直向)優先遍歷陣列。

for(int i = 0; i < 3; i++) for(int j = 0; j < 4; j++) cout << grade[j][i] << endl;

二維陣列其實是很多條一維陣列

不囉嗦!先看圖,而且這是沒有為什麼的!!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

為什麼會說是很多條一維陣列呢?還記得在前面有說,針對一個一維陣列的陣列頭我們有特別的存取方式,像下面這樣。

int score[5]; cout << score; //這樣會印出 score 陣列頭(score[0])的記憶體位置 cout << &score[0] // 這樣也是

那對於二維陣列呢?

int array[4][6]; cout << array; //會印出 array[0][0] 的記憶體位置, //但也同時是 array[0] 的記憶體位置 //同時更是 array 這個陣列的陣列頭 cout << array << " " << array[0] << " " << &array[0][0] << endl; cout << array[1] << " " << &array[1][0] << endl; cout << array[2] << " " << &array[2][0] << endl; cout << array[3] << " " << &array[3][0] << endl;

大家可以在自己的電腦上面執行, array[0] 跟 array[0][0] 的記憶體位置,會是一樣的。所以我們可以得到一個結論,就是二維陣列其實很多條一維陣列,原因就是因為每一條一維陣列的記憶體位置都能夠用陣列頭的方式去存取!

如果今天他不是很多條一維陣列所組成的,是沒辦法取得那麼多陣列頭的!

你問為什麼 array[1] 不用加上 & 符號?
因為他是代表一個陣列的陣列頭啊!
跟一維陣列的例子一樣,相信你一定能懂的!
Orange

如果你還是不懂,我再換個說法,今天有一個名為 array 的二維陣列,它裡面有很多條一維陣列,第一條陣列的第一個記憶體位置 (也就是第一條陣列的陣列頭) 叫做 array[0],第二條陣列的第一個記憶體位置叫做 array[1],依此類推。

而這一整個二維陣列的陣列頭就會是 array[0][0] 的這一格,而對於二維陣列來說,他也有一個陣列頭,就會是 cout << array 所得到的記憶體位置。

避免你不信,我印出一個例子給你看,請看下圖。

int score[4][6]; for(int i = 0; i < 4; i++){ cout << i+1 << "row:\n"; for(int j = 0; j < 6; j++){ cout << &score[i][j] << " "; } cout << endl; }

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

希望到這邊你有看懂,你問為什麼要把陣列談的這麼細?
因為這樣下學期你們學指標(pointer)才好了解,所以強烈希望你能搞懂。
指標是下學期的三大魔王之一哦!
Orange

字串的處理方式其實可以很陣列

假設今天有一個字串如下:

string a = "hello, i\'m Orange";

字串在做處理的時候,能夠像陣列一樣透過索引值去遍歷。
舉例來說,應該都記得陣列的索引值從 0 開始,所以 a[0] 會印出 h,a[1] 會印出 e,依此類推。

No why!
Orange

而這邊有一個小觀念要注意,透過 a[0] 這樣取得而來的字,不是字串(string),而是字元(char)哦!!

有了陣列遍歷的思維(使用 for 迴圈),你就能夠開始針對一個字串去做處理了,舉例來說字串倒轉,你就由後往前遍歷就可以摟。

我已經偷偷提示某一題了哦!
Orange

二維陣列作為參數

假設今天有一個情境,你手邊有一張你們班的成績單,被用陣列儲存起來,然而成績在登記時發生了錯誤,需要請你設計一個函式來幫我們做更改,你可能可以有下列的程式。

  • change_score 函式可以讓我們修改成績
    • 第一個參數為我們的成績單,也就是二維陣列
    • 第二個參數為哪一位同學的成績錯誤,來幫他修改
    • 第三個參數為他的哪一科成績發生了錯誤,要來修改他
  • input_score 函式用來輸入每一位同學的每科成績
    • 第一個參數為我們的成績單,也就是二維陣列
    • 第二個參數為這張成績單紀錄幾位同學
    • 第三個參數為每個人總共要被記錄幾個科目的成績
  • display_score 函式用來檢視某位同學某一科的成績
    • 第一個參數為我們的成績單,也就是二維陣列
    • 第二個參數為要檢視哪一位同學的成績
    • 第三個參數為要檢視哪一個科目
//函式定義 function definition void change_score(int score[][5], int row_loc, int col_loc){ //用 cin 覆蓋掉舊成績 cin >> score[row_loc-1][col_loc-1]; } void input_score(int score[][5], int arr_size_row, int arr_size_col){ for(int i = 0; i < arr_size_row; i++){ cout << "please input the scores of " << i+1 << "th student\n"; for(int j = 0; j < arr_size_col; j++){ //這邊忽略提示語 cin >> score[i][j]; } } } void display_score(const int score[][5], int row_loc, int col_loc){ cout << score[row_loc-1][col_loc-1] << endl; } int main(){ int score_all[10][5];//十位同學,每人皆有五科成績 //function call input_score(score_all, 10, 5); //假設第7位同學的第四個科目的成績發生錯誤 change_score(score_all, 7, 4); //檢視第五位同學的第一個科目 display_score(score_all, 5, 1); return 0; }

Const 關鍵字

相信你眼尖的同學一定可以發現在 display_score 裡面的第一個參數被加上了 const 這個關鍵字,我們下面要來談這個關鍵字。

const 的全名是 constant,他可以有幾個使用情境。

  1. 定義一個常量,常量無法被修改
  2. 在傳參數的時候不希望參數被修改
  3. 其實還有更多情況,但這邊不提

我想透過上面兩點,你已經可以明白為什麼 display_score 的第一個參數要加上 const 了,因為今天這個函式只是做檢視的動作,他不會去改到陣列內的值,那身為一個好的工程師,你的程式要能夠被好好地維護,避免意外的發生,這個時候再傳參數的時候加上一個 const 是非常好的。

const 作為常數變數的宣告

這邊來提第一種情境,就是宣告一個常量,方法是在變數宣告前加上一個 const。

const int size = 5; cin >> size;

如果你這樣寫,你的程式會報錯,因為常量是不允許被修改的。

為參數套上 const 關鍵字

這邊來提第二種情境,針對不想被修改內容的參數,我們可以設定 const 關鍵字。以上面的 display_score 為例

void display_score(const int score[][], int row_loc, int col_loc){ cout << score[row_loc-1][col_loc-1] << endl; //如果你在這裡寫 cin >> score[1][1]; }

第四行這裡的內容,會讓你的編輯器報錯,因為你設定 score 這個陣列參數是 const,是無法被修改的。

小總結
針對常量,比方說

π,我們就可以設成常量
總結來說,在你的程式裡面一個不會變動的變數,就很適合設定成常量。
Orange