CodeGym /課程 /C# SELF /C# 裡的鋸齒狀陣列 (Jagged Arrays)

C# 裡的鋸齒狀陣列 (Jagged Arrays)

C# SELF
等級 7 , 課堂 5
開放

1. 鋸齒狀陣列跟二維陣列有什麼不同

我們終於來到這個主題,很多人會叫它 「陣列的陣列」「鋸齒狀陣列」 —— 英文叫 jagged arrays。跟二維陣列不一樣,鋸齒狀陣列可以存放長度不一樣的欄。就像你有一堆大樓,每棟大樓的公寓數量都不一樣 —— 有一棟有 5 間,另一棟 20 間,還有一棟只有一間。

鋸齒狀陣列 就是陣列裡的每個元素本身又是一個陣列。而且這些內部的陣列(也叫「子陣列」)長度可以不一樣。

最主要的差別:

  • 二維陣列 裡,每一「列」(還有每一「欄」)的元素數量都一樣。舉例:int[,] grid = new int[3, 5]; —— 永遠都是 3 列,每列 5 個元素。
  • 鋸齒狀陣列 裡,每一列可以長度不一樣!舉例:int[][] jagged = new int[3][]; —— 然後你可以自己決定每一列(子陣列)要怎麼初始化。

視覺化起來大概長這樣:

二維陣列 鋸齒狀陣列
元素數量 固定(例如 3x5) 每一列可以不一樣
索引方式
[i, j]
[i][j]
彈性
應用 表格、數學 不規則資料:
學生分數清單、三角形等等

視覺化:比較二維陣列跟鋸齒狀陣列

二維陣列 (3x3):
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘

鋸齒狀陣列 (不同長度):
┌───┬───┐
│ 1 │ 2 │
├───┼───┼───┬───┐
│ 3 │ 4 │ 5 │ 6 │
├───┼───┴───┴───┘
│ 7 │
└───┘

2. 鋸齒狀陣列的宣告和初始化語法

宣告鋸齒狀陣列一點都不可怕!不用怕看到兩層中括號:

int[][] jaggedArray = new int[3][];
鋸齒狀陣列的宣告

這代表我們有一個 3 個元素的陣列,每個元素又是一個 int 陣列。但這時候內部的陣列還沒建立!為了更清楚,我們來細拆一下。

鋸齒狀陣列的逐步初始化

步驟 1 — 建立主要(外部)陣列:

int[][] jaggedArray = new int[3][];

現在我們有 3「列」,但它們目前都是 null

步驟 2 — 建立並填入內部陣列(子陣列):

例如,第一列長度 2,第二列 4,第三列 3:

jaggedArray[0] = new int[2]; // 第一列有 2 個元素
jaggedArray[1] = new int[4]; // 第二列有 4 個元素
jaggedArray[2] = new int[3]; // 第三列有 3 個元素

步驟 3 — 填入值:

內部陣列就是普通陣列!例如:

jaggedArray[0][0] = 1;
jaggedArray[0][1] = 2;

jaggedArray[1][0] = 3;
jaggedArray[1][1] = 4;
jaggedArray[1][2] = 5;
jaggedArray[1][3] = 6;

jaggedArray[2][0] = 7;
jaggedArray[2][1] = 8;
jaggedArray[2][2] = 9;

鋸齒狀陣列的快速初始化

如果你已經知道值,也可以直接建立並填好鋸齒狀陣列:

int[][] jaggedArray = new int[][]
{
    new int[] { 1, 2 },
    new int[] { 3, 4, 5, 6 },
    new int[] { 7, 8, 9 }
};

或者更短一點,省略內部陣列型別:

int[][] jaggedArray = {
    new[] { 1, 2 },
    new[] { 3, 4, 5, 6 },
    new[] { 7, 8, 9 }
};

3. 遍歷和操作鋸齒狀陣列

遍歷鋸齒狀陣列其實跟二維陣列差不多,只是外層 for 走列,內層 for 走每列的元素(每列長度可以不一樣):

for (int i = 0; i < jaggedArray.Length; i++)
{
    Console.WriteLine($"列 {i}:");
    for (int j = 0; j < jaggedArray[i].Length; j++)
    {
        Console.Write($"{jaggedArray[i][j]} ");
    }
    Console.WriteLine();
}

螢幕上的結果:

列 0:
1 2 
列 1:
3 4 5 6 
列 2:
7 8 9 

你也可以用 foreach,不用自己管索引:

foreach (int[] row in jaggedArray)
{
    foreach (int value in row)
    {
        Console.Write($"{value} ");
    }
    Console.WriteLine();
}

4. 陣列的陣列的內部結構

現在你要知道,陣列的陣列到底是怎麼運作的。準備好了嗎?

如果是普通陣列,「陣列變數存的是一個容器的參考,這個容器存放陣列元素」。但鋸齒狀陣列就有點爆炸:陣列的陣列變數存的是一個容器的參考,這個容器裡存的是一堆一維陣列的參考。這種東西還是看圖比較容易懂:

How two-dimensional arrays work

左邊 是「陣列的陣列變數」,它存著「陣列容器物件」的參考。中間 是「陣列容器物件」,每個格子裡存著一維陣列的參考 —— 也就是鋸齒狀陣列的列。然後 右邊 你看到四個一維陣列 —— 就是我們鋸齒狀陣列的列。

這就是鋸齒狀陣列的真實結構。這種設計給 C# 程式設計師幾個好處:

第一,因為「容器的容器」存的是「列陣列」的參考,我們可以很快地交換列。要存取「容器的容器」,只要一個索引,不用兩個。舉例:
int[][] data = new int[2][];
data[0] = new int[5]; // 第一列是 5 個元素的陣列
data[1] = new int[5]; // 第二列也是 5 個元素的陣列

int[] row1 = data[0];
int[] row2 = data[1];

這樣就可以交換列:

// 重要的資料矩陣
int[][] matrix = {
  new int[] {1, 2, 3, 4, 5},
  new int[] {5, 4, 3, 2, 1}
};

int[] tmp = matrix[0];
matrix[0] = matrix[1];
matrix[1] = tmp;

如果你只用一個索引存取二維陣列的元素,其實你拿到的是「容器的容器」,裡面存著一維陣列的參考。

5. 鋸齒狀陣列的常見應用場景

什麼時候鋸齒狀陣列比二維陣列好用?

  • 如果你要存每個使用者不同數量的資料:像是各科分數、購買紀錄、留言等等。
  • 如果你的資料本來就是三角形或階梯狀(像是要印金字塔、Pascal 三角形等等)。
  • 如果你想省記憶體:二維陣列每列都固定長度,鋸齒狀陣列只存需要的元素。

生活範例:學生分數管理員

來擴充一下我們的學習專案吧!假設每個學生每科的分數數量都不一樣,有人交多,有人交少。這時候鋸齒狀陣列就超適合。

假設我們有三個學生,他們的數學作業分數如下:

學生 分數
0 5, 4
1 3, 4, 4
2 5

宣告這樣的陣列:

int[][] studentMarks = new int[3][];
studentMarks[0] = new int[] { 5, 4 };         // 第一個學生 - 2 個分數
studentMarks[1] = new int[] { 3, 4, 4 };      // 第二個學生 - 3 個分數
studentMarks[2] = new int[] { 5 };            // 第三個學生 - 1 個分數

印出每個學生的分數:

for (int i = 0; i < studentMarks.Length; i++)
{
    Console.Write($"學生 {i}: ");
    for (int j = 0; j < studentMarks[i].Length; j++)
    {
        Console.Write(studentMarks[i][j] + " ");
    }
    Console.WriteLine();
}

鋸齒狀陣列也可以用其他型別

鋸齒狀陣列可以是任何東西:字串、甚至更深的陣列,或你自己的物件。

範例:字串陣列

string[][] groups = new string[][]
{
    new string[] { "伊萬", "彼得" },
    new string[] { "瑪麗亞", "阿列克謝", "謝爾蓋" },
    new string[] { "瓦西莉薩" }
};

6. 特點與常見錯誤

鋸齒狀陣列很彈性,但也有不少陷阱。

  • 如果你沒初始化某個內部陣列(jaggedArray[1] = ...),去存它會噴 NullReferenceException。記得每個內部陣列都要初始化!
  • 不是每一列(子陣列)長度都一樣。如果你用固定索引去存第二層,可能會超出範圍。
  • 不要搞混二維陣列!索引要寫成 array[i][j],不是 array[i, j]
1
問卷/小測驗
陣列,等級 7,課堂 5
未開放
陣列
認識陣列
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION