1. 鋸齒狀陣列跟二維陣列有什麼不同
我們終於來到這個主題,很多人會叫它 「陣列的陣列」 或 「鋸齒狀陣列」 —— 英文叫 jagged arrays。跟二維陣列不一樣,鋸齒狀陣列可以存放長度不一樣的欄。就像你有一堆大樓,每棟大樓的公寓數量都不一樣 —— 有一棟有 5 間,另一棟 20 間,還有一棟只有一間。
鋸齒狀陣列 就是陣列裡的每個元素本身又是一個陣列。而且這些內部的陣列(也叫「子陣列」)長度可以不一樣。
最主要的差別:
- 在 二維陣列 裡,每一「列」(還有每一「欄」)的元素數量都一樣。舉例:int[,] grid = new int[3, 5]; —— 永遠都是 3 列,每列 5 個元素。
- 在 鋸齒狀陣列 裡,每一列可以長度不一樣!舉例:int[][] jagged = new int[3][]; —— 然後你可以自己決定每一列(子陣列)要怎麼初始化。
視覺化起來大概長這樣:
| 二維陣列 | 鋸齒狀陣列 | |
|---|---|---|
| 元素數量 | 固定(例如 3x5) | 每一列可以不一樣 |
| 索引方式 | |
|
| 彈性 | 低 | 高 |
| 應用 | 表格、數學 | 不規則資料: 學生分數清單、三角形等等 |
視覺化:比較二維陣列跟鋸齒狀陣列
二維陣列 (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. 陣列的陣列的內部結構
現在你要知道,陣列的陣列到底是怎麼運作的。準備好了嗎?
如果是普通陣列,「陣列變數存的是一個容器的參考,這個容器存放陣列元素」。但鋸齒狀陣列就有點爆炸:陣列的陣列變數存的是一個容器的參考,這個容器裡存的是一堆一維陣列的參考。這種東西還是看圖比較容易懂:
左邊 是「陣列的陣列變數」,它存著「陣列容器物件」的參考。中間 是「陣列容器物件」,每個格子裡存著一維陣列的參考 —— 也就是鋸齒狀陣列的列。然後 右邊 你看到四個一維陣列 —— 就是我們鋸齒狀陣列的列。
這就是鋸齒狀陣列的真實結構。這種設計給 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]。
GO TO FULL VERSION