CodeGym /课程 /C# SELF /C#中的锯齿数组(Jagged Arrays)

C#中的锯齿数组(Jagged Arrays)

C# SELF
第 7 级 , 课程 5
可用

1. 锯齿数组和二维数组有啥不一样

我们终于聊到一个很多人叫做“数组的数组”或者“锯齿数组”的话题——英文里叫jagged arrays。和二维数组不一样,锯齿数组可以存储长度不一样的列。就像你有一堆楼,每栋楼的房子数量都不一样——有的楼5个房子,有的20个,还有的就1个。

锯齿数组其实就是一个数组,它的每个元素本身又是一个数组。而且这些内部的数组(也叫“子数组”)长度可以完全不一样。

最主要的区别:

  • 二维数组里,每一“行”(还有每一“列”)的元素数量都是一样的。比如: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 (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. 锯齿数组的常见应用场景

什么时候锯齿数组比二维数组更香?

  • 如果你要给每个用户存不一样数量的数据:比如每门课的成绩、购物记录、评论啥的。
  • 如果你的数据本身就是三角形、阶梯形结构(比如输出金字塔、帕斯卡三角形啥的)。
  • 如果你想省内存:二维数组每行都固定长度,锯齿数组只分配需要的元素。

生活中的例子:学生成绩管理器

来扩展一下我们的学习项目!假设每个学生每门课的成绩数量都不一样。比如有的人交作业多,有的人少。锯齿数组就很适合。

假设我们有三个学生,他们的数学作业成绩如下:

学生 成绩
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