CSharp(十六) 交错数组
C# 中的交错数组(Jagged Array)是数组的数组,即每个元素本身也是一个数组。与矩形多维数组不同,交错数组的每个子数组可以具有不同长度。
定义
交错数组(Jagged Array),也称为锯齿数组,是一种数组的数组。它的每个元素都是一个一维数组,且这些子数组的长度可以各不相同。
声明语法
// 二维交错数组
dataType[][] arrayName;
// 三维交错数组(数组的数组的数组)
dataType[][][] arrayName;
// 更复杂的嵌套形式
dataType[][][,] arrayName; // 交错数组,每个元素是矩形二维数组
创建与初始化
// 1. 分步创建:先声明外层数组,再逐个创建内层数组
int[][] jagged = new int[3][]; // 外层数组有3个元素
jagged[0] = new int[4]; // 第1个子数组有4个元素
jagged[1] = new int[2]; // 第2个子数组有2个元素
jagged[2] = new int[5]; // 第3个子数组有5个元素
// 2. 声明时直接初始化
int[][] jagged2 = new int[][] {
new int[] {1, 2, 3, 4},
new int[] {5, 6},
new int[] {7, 8, 9, 10, 11}
};
// 3. 简写形式(省略 new int[][])
int[][] jagged3 = {
new int[] {1, 2, 3, 4},
new int[] {5, 6},
new int[] {7, 8, 9, 10, 11}
};
// 4. 更复杂的交错数组
int[][][] threeD = new int[2][][];
threeD[0] = new int[][] {
new int[] {1, 2},
new int[] {3, 4, 5}
};
threeD[1] = new int[][] {
new int[] {6, 7, 8},
new int[] {9}
};
使用(增删改查)
查(访问元素)
int[][] jagged = {
new int[] {1, 2, 3, 4},
new int[] {5, 6},
new int[] {7, 8, 9, 10, 11}
};
// 访问单个元素
int value = jagged[0][2]; // 第0个子数组的第2个元素:3
Console.WriteLine(jagged[1][0]); // 输出:5
Console.WriteLine(jagged[2][4]); // 输出:11
// 获取子数组长度
int len0 = jagged[0].Length; // 4
int len1 = jagged[1].Length; // 2
int len2 = jagged[2].Length; // 5
// 获取外层数组长度
int outerLen = jagged.Length; // 3
// 安全访问(防止 NullReferenceException)
if (jagged[1] != null && jagged[1].Length > 0)
{
Console.WriteLine(jagged[1][0]);
}
改(修改元素)
int[][] jagged = {
new int[] {1, 2, 3, 4},
new int[] {5, 6},
new int[] {7, 8, 9, 10, 11}
};
// 修改现有元素
jagged[0][0] = 100; // 将第0行第0列改为100
jagged[1][1] = 200; // 将第1行第1列改为200
// 替换整个子数组
jagged[1] = new int[] {50, 60, 70}; // 第1行现在变为3个元素
// 使用 Array 方法修改
Array.Reverse(jagged[0]); // 反转第0个子数组:{4, 3, 2, 1}
Array.Sort(jagged[2]); // 排序第2个子数组
增(添加元素/扩展)
交错数组本身大小固定,但可以通过创建新数组或操作子数组来实现"添加"效果:
// 方法1:替换子数组为更大的数组(添加元素到某一行)
int[][] jagged = {
new int[] {1, 2, 3},
new int[] {4, 5}
};
// 扩展第0行:创建新数组并复制
int[] newRow = new int[jagged[0].Length + 2];
Array.Copy(jagged[0], newRow, jagged[0].Length);
newRow[jagged[0].Length] = 100; // 添加新元素
newRow[jagged[0].Length + 1] = 200;
jagged[0] = newRow; // 替换原数组
// 结果:jagged[0] = {1, 2, 3, 100, 200}
// 方法2:添加新行(创建更大的外层数组)
int[][] newJagged = new int[jagged.Length + 1][];
Array.Copy(jagged, newJagged, jagged.Length);
newJagged[jagged.Length] = new int[] {10, 20, 30};
jagged = newJagged;
// 结果:jagged 现在有3行
// 方法3:使用 List<int[]> 作为动态替代(更灵活)
List<int[]> list = new List<int[]>();
list.Add(new int[] {1, 2, 3});
list.Add(new int[] {4, 5});
list.Add(new int[] {6, 7, 8, 9}); // 动态添加行
list[0] = new int[] {10, 20}; // 修改某行
删(删除元素)
int[][] jagged = {
new int[] {1, 2, 3, 4},
new int[] {5, 6, 7},
new int[] {8, 9},
new int[] {10, 11, 12, 13}
};
// 方法1:删除某行(创建新数组,跳过要删除的行)
int removeIndex = 1; // 删除第1行
int[][] newJagged = new int[jagged.Length - 1][];
for (int i = 0, j = 0; i < jagged.Length; i++)
{
if (i != removeIndex)
{
newJagged[j++] = jagged[i];
}
}
jagged = newJagged;
// 方法2:清空某行(将子数组设为 null 或空数组)
jagged[2] = null; // 设为 null(需注意后续访问判空)
jagged[2] = new int[0]; // 设为空数组
// 方法3:删除某行中的某个元素
int[] row = jagged[0];
int removeCol = 2; // 删除第2列
int[] newRow = new int[row.Length - 1];
Array.Copy(row, 0, newRow, 0, removeCol);
Array.Copy(row, removeCol + 1, newRow, removeCol, row.Length - removeCol - 1);
jagged[0] = newRow;
遍历交错数组
int[][] jagged = {
new int[] {1, 2, 3, 4},
new int[] {5, 6},
new int[] {7, 8, 9, 10, 11}
};
// 方法1:使用嵌套 for 循环(推荐,可处理 null 子数组)
for (int i = 0; i < jagged.Length; i++)
{
if (jagged[i] == null) continue;
for (int j = 0; j < jagged[i].Length; j++)
{
Console.Write($"{jagged[i][j]} ");
}
Console.WriteLine();
}
// 输出:
// 1 2 3 4
// 5 6
// 7 8 9 10 11
// 方法2:使用 foreach(更简洁)
foreach (int[] row in jagged)
{
if (row == null) continue;
foreach (int item in row)
{
Console.Write($"{item} ");
}
Console.WriteLine();
}
// 方法3:使用 LINQ(需要 using System.Linq)
var allItems = jagged.SelectMany(row => row ?? Array.Empty<int>());
Console.WriteLine(string.Join(", ", allItems));
// 输出:1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
交错数组作为方法参数
// 接收交错数组
void PrintJaggedArray(int[][] jagged)
{
for (int i = 0; i < jagged.Length; i++)
{
if (jagged[i] == null) continue;
for (int j = 0; j < jagged[i].Length; j++)
{
Console.Write($"{jagged[i][j]} ");
}
Console.WriteLine();
}
}
// 返回交错数组
int[][] CreateTriangle(int rows)
{
int[][] triangle = new int[rows][];
for (int i = 0; i < rows; i++)
{
triangle[i] = new int[i + 1];
for (int j = 0; j <= i; j++)
{
triangle[i][j] = j + 1;
}
}
return triangle;
}
// 结果:
// 1
// 1 2
// 1 2 3
注意事项
1. 交错数组 vs 矩形数组
| 特性 | 交错数组 int[][] |
矩形数组 int[,] |
|---|---|---|
| 内存布局 | 不连续(数组的数组) | 连续内存块 |
| 每行长度 | 可以不同 | 必须相同 |
| 声明方式 | int[][] |
int[,] |
| 初始化 | new int[3][] |
new int[3,4] |
| 访问语法 | arr[i][j] |
arr[i,j] |
| 性能 | 访问略慢(两次寻址) | 访问更快(缓存友好) |
| 灵活性 | 高(每行可独立调整) | 低(结构固定) |
2. 子数组可能为 null
交错数组创建后,子数组默认为 null,必须单独初始化:
int[][] jagged = new int[3][];
// 此时 jagged[0], jagged[1], jagged[2] 都是 null
// 必须逐个初始化
jagged[0] = new int[4];
jagged[1] = new int[2];
jagged[2] = new int[5];
// 访问前必须检查 null
// Console.WriteLine(jagged[0][0]); // 如果未初始化会抛出 NullReferenceException
3. 索引访问方式不同
交错数组使用两个方括号,矩形数组使用逗号分隔:
int[][] jagged = {{1, 2}, {3, 4, 5}};
int[,] rect = {{1, 2, 3}, {4, 5, 6}};
// 交错数组
int a = jagged[0][1]; // 正确
// int b = jagged[0, 1]; // 错误!编译失败
// 矩形数组
int c = rect[0, 1]; // 正确
// int d = rect[0][1]; // 错误!编译失败
4. 初始化器语法差异
// 交错数组:每个子数组必须显式使用 new int[]
int[][] jagged = {
new int[] {1, 2, 3},
new int[] {4, 5}
};
// 矩形数组:可以直接使用嵌套花括号
int[,] rect = {
{1, 2, 3},
{4, 5, 6}
};
5. 交错数组本身是固定大小的
交错数组的外层数组长度固定,不能直接添加或删除行:
int[][] jagged = new int[3][];
// jagged.Length 永远是 3
// 需要"添加"行时,必须创建新数组
int[][] newJagged = new int[4][];
Array.Copy(jagged, newJagged, 3);
newJagged[3] = new int[] {1, 2, 3};
jagged = newJagged; // 重新引用
6. 子数组可以替换为不同长度的数组
虽然外层数组固定,但子数组可以被替换:
int[][] jagged = {
new int[] {1, 2, 3},
new int[] {4, 5}
};
// 替换第0行为更长的数组
jagged[0] = new int[] {10, 20, 30, 40, 50};
// 现在 jagged[0].Length 是 5
7. 内存布局与性能
交错数组在内存中不连续,访问需要两次寻址:
int[][] jagged = new int[1000][];
for (int i = 0; i < 1000; i++)
{
jagged[i] = new int[1000];
}
// 访问 jagged[i][j] 的过程:
// 1. 先找到 jagged[i](外层数组索引)
// 2. 再在 jagged[i] 中找到第 j 个元素
// 比矩形数组 arr[i, j] 多一次寻址
// 遍历时,矩形数组通常更快(缓存友好)
int[,] rect = new int[1000, 1000]; // 连续内存
8. 与 Array 类和 LINQ 的交互
int[][] jagged = {
new int[] {3, 1, 2},
new int[] {6, 4, 5}
};
// 对单个子数组使用 Array 方法
Array.Sort(jagged[0]); // 排序第0行:{1, 2, 3}
Array.Reverse(jagged[1]); // 反转第1行:{5, 4, 6}
// 使用 LINQ 处理整个交错数组
using System.Linq;
// 获取所有元素
var all = jagged.SelectMany(row => row);
// 获取总行数
int totalRows = jagged.Length;
// 获取总元素数
int totalElements = jagged.Sum(row => row.Length);
// 获取最长行的长度
int maxLen = jagged.Max(row => row.Length);
// 筛选非空行
var nonEmpty = jagged.Where(row => row != null && row.Length > 0);
9. 混合使用:交错数组包含矩形数组
// 交错数组的元素可以是矩形数组
int[][,] mixed = new int[2][,];
mixed[0] = new int[,] {
{1, 2, 3},
{4, 5, 6}
};
mixed[1] = new int[,] {
{7, 8},
{9, 10},
{11, 12}
};
// 访问方式
int a = mixed[0][1, 2]; // 第0个矩形数组的第1行第2列:6
int b = mixed[1][2, 0]; // 第1个矩形数组的第2行第0列:11
10. 常见错误
int[][] jagged = new int[3][];
// 错误1:未初始化子数组就访问
// int x = jagged[0][0]; // NullReferenceException
// 错误2:使用矩形数组的语法
// int y = jagged[0, 0]; // 编译错误
// 错误3:初始化器缺少 new int[]
// int[][] bad = {{1, 2}, {3, 4}}; // 编译错误
// 错误4:索引越界
jagged[0] = new int[] {1, 2};
// int z = jagged[0][5]; // IndexOutOfRangeException
// 错误5:假设所有子数组长度相同
for (int i = 0; i < jagged.Length; i++)
{
// 危险:如果某行比其他行短,会越界
for (int j = 0; j < jagged[0].Length; j++) // 应该用 jagged[i].Length
{
Console.WriteLine(jagged[i][j]);
}
}
总结
交错数组适合表示每行长度不固定的数据,如:
- 三角形矩阵
- 稀疏矩阵
- 不同长度的字符串列表
- 树形结构的层级数据
其特点是:
- 使用
[][]声明和访问,而非[,] - 每个子数组长度可以不同
- 子数组默认为 null,必须单独初始化
- 内存不连续,访问效率略低于矩形数组
- 灵活性高,适合不规则数据结构
- 外层数组大小固定,但子数组可替换