作为 Codegym 大学课程一部分的导师授课片段。报名参加完整课程。


“你好,阿米戈!”

“你好,瑞希!”

“你已经对数组了解一两件事,我希望你甚至设法解决了一些任务。但你并不是什么都知道。例如,这是关于数组的另一个有趣的事实。数组不仅是一维的(线性的) ). 它们也可以是二维的。

“嗯……什么意思?”

“这意味着数组的单元格不仅可以代表一列(或行),还可以代表一个矩形表格。

int[][]name = new int[width][height];

"其中name是数组变量的名称,width是表格宽度(以单元格为单位),height是表格高度。看一个例子:

int[][] data = new int[2][5];
data[1][1] = 5;
我们创建一个二维数组:2 列和 5 行。
我们在单元格 (1,1) 中写入 5。

“这就是它在内存中的样子:

二维数组

“顺便说一句,对于二维数组,你也可以使用快速初始化:

// Lengths of months of the year in each quarter
int[][] months = { {31, 28, 31}, {30, 31, 30}, {31, 31, 30}, {31, 30, 31} };

“嗯……现在这很有趣。如果我们想象在第一个内括号中代表一个元素,下一个是第二个……那么二维数组就像数组的数组?”

“你真是个聪明的学生!没错。第一个元素是一维数组{31, 28, 31},第二个是{30, 31, 30},依此类推。但我们稍后会在本课中回过头来讨论这个问题。在那之前,试着想一想作为具有行和列的表格的二维数组,在每个交叉点形成单元格。

“我心里有数了。顺便问一下,这些二维阵列是干什么用的?”

“程序员经常需要二维数组。如果仔细观察,几乎所有棋盘游戏都是使用现成的二维数组实现的:国际象棋、西洋跳棋、井字棋、海战等:”

海战

“我明白了!象棋或者海战的比赛场地,完全适合二维阵法!”

“是的,但你需要使用数字作为单元格坐标。不是‘pawn e2-e4’,而是‘pawn (5,2) -> (5,4)’。作为程序员,这对你来说会更容易。 “

排列数组中的元素:(x, y) 或 (y, x)

“创建二维数组会引发一个有趣的难题。当我们使用创建数组时,我们有一个‘两5new int [2][5];’的表格还是‘两列 5 行’?”

“换句话说,目前还不完全清楚我们是先指定宽度然后再指定高度……还是相反,先指定高度再指定宽度?”

“是的,这就是困境。而且没有确定的答案。”

“该怎么办?”

“首先,重要的是要了解我们的二维数组实际上是如何存储在内存中的。自然地,计算机内存中实际上并没有任何表格:内存中的每个位置都有一个连续的数字地址:0、1、2, ...对我们来说,这是一个 2 × 5 的表格,但在内存中它只是 10 个单元格,仅此而已。没有划分行和列。”

“我明白了。那么我们如何确定哪个尺寸在前——宽度还是高度?”

“让我们考虑第一个选项。首先是宽度,然后是高度。”赞成这种方法的论据是这样的:每个人在学校学习数学,他们都知道坐标对写成'x'(即水平轴)然后是“y”(垂直维度)。这不仅仅是一个学校标准——它是一个普遍接受的数学标准。正如他们所说,你不能与数学争论。”

「是吗?好吧,既然打不过,那就先宽后高吧?」

“有一个支持‘先高后宽’的有趣论点。这个论点来自于二维数组的快速初始化。毕竟,如果我们想初始化我们的数组,那么我们会这样写代码:”

// Matrix of important data
int[][] matrix = { {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5} };

“那对我们有什么用呢?”

“你有没有注意到什么?如果我们有这个怎么办?

// Matrix of important data
int[][] matrix = {
  {1, 2, 3, 4, 5},
  {1, 2, 3, 4, 5}
};

“如果我们在代码中逐行写入我们的数据,那么我们会得到一个包含 2 行和 5 列的表格。”

“现在我明白了。2是高度,5是宽度……那么我们应该使用哪个选项呢?”

“由你来决定哪个更方便。最重要的是所有从事同一个项目的程序员都坚持同一种方法。”

“如果你在一个项目中工作,它的代码有很多初始化的二维数组,那么很可能那里的一切都基于快速数据初始化,即你将拥有标准的‘高度 x 宽度’。

“如果你发现自己在一个涉及大量数学和坐标的项目中(例如,游戏引擎),那么代码很可能会采用“宽 x 高”的方法。

二维数组是怎么排列的

“现在,你还记得你在课程开始时注意到的二维数组的特殊性吗?”

“对!就是二维阵法,其实就是阵法的阵法!”

“完全正确。”换句话说,如果在普通数组的情况下,数组变量存储对存储数组元素的容器的引用,那么在二维数组的情况下,情况有点爆炸:二维-array 变量存储对容器的引用,该容器存储对一维数组的引用。最好只看一次,而不是尝试解释一百遍:”

二维数组是怎么排列的

“在左边,我们有一个二维数组变量,它存储了对二维数组对象的引用。在中间一个二维数组对象,其单元格存储一维数组,这是二维数组的行。在右边你可以看到四个一维数组——我们的二维数组的行。这就是二维数组的实际工作方式。”

“太棒了!但它给了我们什么?”

“由于‘容器的容器’存储对‘行数组’的引用,我们可以非常快速、轻松地交换行。要获得‘容器的容器’,您只需指定一个索引而不是两个。示例:

int[][] data = new int[2][5];
int[] row1 = data[0];
int[] row2 = data[1];

“看下面的代码。我们可以用它来交换行:”

// Matrix of important data
int[][] matrix = {
  {1, 2, 3, 4, 5},
  {5, 4, 3, 2, 1}
};

int[] tmp = matrix[0];
matrix[0] = matrix[1];
matrix[1] = tmp;
二维数组





matrix[0]存储对第一行的引用。
我们交换参考。

结果,matrix数组看起来像这样:
{
  {5, 4, 3, 2, 1},
  {1, 2, 3, 4, 5}
};

“明白了。就像交换任意两个普通物品一样。”

“确实如此。好吧,如果你引用二维数组的一个单元格,但你只在数组名称后指定一个索引,那么你指的是一个容器的容器,其单元格存储对普通一-维数组。”

“一切似乎都合乎逻辑且清晰。感谢您的演讲,Rishi!”

“不客气。明智地付诸实践。”