← 编程学习中心
📖

Preparation

8 个课程
1
如何学习编程
2
计算机基础
3
键盘初识与基准键位
4
字母与数字基础
5
Shift键魔法
6
编程符号专精
7
功能键与修饰键
8
键盘学习总结与进阶
📚

Basics

22 个课程
1
什么是编程?
2
Hello World - 你的第一个程序
3
变量基础概念 - 给信息贴标签
4
变量赋值和操作 - 变量的高级用法
5
数据类型基础 - 数字和字符串
6
基础运算符 - 数字运算王国
7
布尔类型与比较运算符 - 程序的判断基础
8
条件判断基础 - 让程序学会做决定
9
变量作用域 - 变量的生存范围
10
条件语句进阶 - 复杂判断与嵌套结构
11
逻辑运算符 - 简化复杂条件判断
12
循环语句 - 程序的重复机器
13
循环进阶 - break、continue与循环控制
14
嵌套循环与作用域
15
循环调试实战技巧
16
循环不变量 - 理解循环的数学规律
17
数组基础 - 数据的收纳盒
18
数组操作方法 - 添加、删除和管理
19
数组高级 - 探索数组的强大功能
20
引用概念 - 变量的不同行为
21
函数基础 - 解决重复代码的秘密武器
22
函数实践与应用 - 巩固基础,实战应用
🚀

Intermediate

11 个课程
1
从控制台到画布 - p5.js初体验
2
图形绘制工具箱 - 基础图形函数
3
让世界多彩 - 颜色系统详解
4
文字的艺术 - 文本绘制与样式
5
球体动起来 - 动画基础入门
6
球体的交互魔法 - 鼠标与条件判断
7
面向对象编程 - Class类与实例
8
球的家族 - 循环与数组应用
9
球的个性 - 函数封装与参数化
10
球的变身 - 形状模式与高级绘制
11
球的世界 - 综合创作项目
🎯

Advanced

6 个课程
1
对象类型
2
对象类型应用
3
自定义类型
4
函数进阶 - 箭头函数与高级编程技巧
5
内置方法 - 字符串与数组的魔法工具
6
Switch语句与字符串模板 - 更优雅的代码表达
← 编程学习中心
📖

Preparation

8 个课程
1
如何学习编程
2
计算机基础
3
键盘初识与基准键位
4
字母与数字基础
5
Shift键魔法
6
编程符号专精
7
功能键与修饰键
8
键盘学习总结与进阶
📚

Basics

22 个课程
1
什么是编程?
2
Hello World - 你的第一个程序
3
变量基础概念 - 给信息贴标签
4
变量赋值和操作 - 变量的高级用法
5
数据类型基础 - 数字和字符串
6
基础运算符 - 数字运算王国
7
布尔类型与比较运算符 - 程序的判断基础
8
条件判断基础 - 让程序学会做决定
9
变量作用域 - 变量的生存范围
10
条件语句进阶 - 复杂判断与嵌套结构
11
逻辑运算符 - 简化复杂条件判断
12
循环语句 - 程序的重复机器
13
循环进阶 - break、continue与循环控制
14
嵌套循环与作用域
15
循环调试实战技巧
16
循环不变量 - 理解循环的数学规律
17
数组基础 - 数据的收纳盒
18
数组操作方法 - 添加、删除和管理
19
数组高级 - 探索数组的强大功能
20
引用概念 - 变量的不同行为
21
函数基础 - 解决重复代码的秘密武器
22
函数实践与应用 - 巩固基础,实战应用
🚀

Intermediate

11 个课程
1
从控制台到画布 - p5.js初体验
2
图形绘制工具箱 - 基础图形函数
3
让世界多彩 - 颜色系统详解
4
文字的艺术 - 文本绘制与样式
5
球体动起来 - 动画基础入门
6
球体的交互魔法 - 鼠标与条件判断
7
面向对象编程 - Class类与实例
8
球的家族 - 循环与数组应用
9
球的个性 - 函数封装与参数化
10
球的变身 - 形状模式与高级绘制
11
球的世界 - 综合创作项目
🎯

Advanced

6 个课程
1
对象类型
2
对象类型应用
3
自定义类型
4
函数进阶 - 箭头函数与高级编程技巧
5
内置方法 - 字符串与数组的魔法工具
6
Switch语句与字符串模板 - 更优雅的代码表达

数组高级 - 探索数组的强大功能

🎯 从一维到二维:自然的演进

在数组基础课程中,我们学习了数组的每个元素都可以存储数据。那么,如果数组的每个元素又是一个数组,会发生什么呢?

💡 一个自然的思考

让我们从一个简单的思考开始:

加载代码编辑器...

如果每个元素不是简单的字符串,而是数组呢?

加载代码编辑器...

现在我们有了一个"数组中的数组"!这就是二维数组的由来。

📊 理解二维数组的概念

二维数组 = 数组的每个元素又是一个数组

加载代码编辑器...

二维数组访问的核心原理

classroom[0][1] 的执行过程:

分步执行:

  1. classroom[0] → 获取第0个元素:["小明", "小红", "小刚"]
  2. ["小明", "小红", "小刚"][1] → 从该数组中获取第1个元素:"小红"

最终结果: classroom[0][1] = "小红"

为什么需要二维数组?

现实世界的数据经常有结构,适合用二维数组存储:

  • 表格数据:学生成绩表、课程表
  • 网格数据:游戏地图、棋盘
  • 分组数据:按班级或分组组织的信息

一维数组的局限性:

加载代码编辑器...

二维数组的优势:

加载代码编辑器...

记忆口诀:

  • 一维数组:一条线,一个索引 array[index]
  • 二维数组:一张表,两个索引 array[row][col]
  • 第一个索引:选行(第几组/第几排)
  • 第二个索引:选列(该组的第几个)

现在你已经理解了二维数组是如何从"数组的元素是数组"这个自然想法演变而来的,接下来让我们学习二维数组的类型声明!

🎯 二维数组的类型声明

为什么需要二维数组类型声明?

和一维数组一样,当我们需要创建空数组时,显式声明类型很重要:

加载代码编辑器...

🔍 认识二维数组类型语法:string[][]

这是你第一次接触 string[][] 这样的二维数组类型语法!让我们详细理解一下:

二维数组有两种等价的写法:

写法1:(string[])[] - 括号写法(容易理解)

让我们先看这种写法:

加载代码编辑器...

理解 (string[])[]:

  • [](右边):表示这是一个数组
  • (string[])(左边):表示数组中每个元素的类型是字符串数组
  • 合起来:由字符串数组组成的数组

写法2:string[][] - 简洁写法(常用)

加载代码编辑器...

string[][] 是 (string[])[] 的简化写法:

  • 更简洁:不需要写括号
  • 更常用:大多数开发者喜欢用这种写法
  • 完全等价:和 (string[])[] 的功能完全一样

为什么可以这样简化?

  • TypeScript 知道 [][] 这种写法表示"数组的数组"
  • string[][] 自然就理解为"字符串数组的数组"
  • 这就像 2 + 3 + 4 和 (2 + 3) + 4 的结果是一样的

实际使用建议:

  • 学习时:先理解 (string[])[] 的含义
  • 写代码时:直接使用 string[][],因为它更简洁
  • 看到别人代码:两种写法都要能认识

所以 string[][] 的意思是:

  • 数组:外层是一个数组(存放多个元素)
  • 每个元素都是字符串数组:里面的每个元素又是一个字符串数组
  • 整体概念:"数组的数组" → 二维数组

实际例子理解

加载代码编辑器...

现在你已经掌握了二维数组的类型声明,接下来让我们学习二维数组的具体操作方法!

二维数组:核心操作技能

🔍 获取二维数组的基本信息

在学习如何访问二维数组元素之前,我们首先需要知道如何获取数组的"形状"——有多少行和多少列。

加载代码编辑器...

关键理解:

  1. 获取行数:array.length

    • 二维数组的行数 = 外层数组的长度
    • scores.length = 3 → 表示有3行数据
  2. 获取列数:array[0].length

    • array[0]:获取第0行(第一个数组)
    • .length:获取这个第0行数组的长度
    • scores[0].length = 3 → 表示第0行有3个元素,即每行有3列
    • 假设每行的列数相同(规则的二维数组)

为什么用第0行获取列数? 在规则的二维数组中,所有行的列数都是相同的:

  • scores[0].length = 3
  • scores[1].length = 3
  • scores[2].length = 3
  • 所以用哪一行的长度都可以,习惯上用第0行

访问二维数组的基本语法

语法格式: array[rowIndex][colIndex]

  • rowIndex:行的索引(从0开始)
  • colIndex:列的索引(从0开始)
  • 访问顺序:先选择行,再选择列

实际例子:学生成绩表

加载代码编辑器...

为什么 scores[3][0] 会报错?

让我们一步步分析 scores[3][0] 的执行过程:

加载代码编辑器...

理解关键点:

  • scores[3] 返回 undefined(因为没有第4个学生)
  • undefined[0] 等于"给 undefined 取第0个属性",这会立即报错
  • 任何对 undefined 的属性访问(包括 [索引])都会报错

关键规则:对 undefined 的操作限制

  1. ✅ 安全的操作:只能进行相等比较

    加载代码编辑器...
  2. ❌ 危险的操作:任何属性访问或方法调用

    加载代码编辑器...

二维数组访问的三种情况:

  1. ✅ 安全:scores[0][3] → 返回 undefined

    • 原因:scores[0] 是数组,访问不存在的索引返回 undefined
  2. ✅ 安全:scores[3] → 返回 undefined

    • 原因:直接返回 undefined,没有进一步操作
  3. ❌ 报错:scores[3][0] → 运行时报错

    • 原因:scores[3] 是 undefined,undefined[0] 违反了对 undefined 的操作规则

记忆重点:

  • undefined 只能比较,不能访问属性!
  • 任何 undefined.属性 或 undefined[索引] 都会报错
  • 这就是为什么二维数组访问要特别小心行索引的原因

安全访问模式:

加载代码编辑器...

最佳实践建议:

  1. 先获取数组大小:totalRows = array.length, totalCols = array[0].length
  2. 检查索引范围:rowIndex >= 0 && rowIndex < totalCols 和 colIndex >= 0 && colIndex < totalCols
  3. 再安全访问:只有在索引有效时才访问元素

如何修改二维数组元素

修改二维数组元素与访问类似,也需要使用两个索引来精确定位要修改的位置。

🔧 修改二维数组元素的基本语法

语法格式: array[rowIndex][colIndex] = newValue

  • 先访问:使用 array[rowIndex][colIndex] 找到目标位置
  • 后赋值:使用 = 将新值赋给该位置

实际例子:井字棋游戏

加载代码编辑器...

理解游戏符号

  • ⭕ 和 ❌ 不是图片,而是特殊的文字(就像😊👍这样的表情符号)
  • 这些特殊文字需要特殊的方式才能输入
  • 它们可以在控制台中正常显示,让棋盘更直观

逐步理解修改过程:

1. 为什么使用 join()?

  • 直接打印数组:["⭕", "❌", "⭕"] - 显示方括号和逗号,格式混乱
  • 使用 join(" "):⭕ ❌ ⭕ - 用空格分隔,格式清晰美观
  • 目的:让棋盘显示得像真正的棋盘,而不是程序数据

2. 数组修改的过程:

  • gameBoard[1][1] → 找到第2行第2列的位置(当前是"⭕")
  • gameBoard[1][1] = "❌" → 将该位置的值改为"❌"
  • 修改立即生效,但数组结构保持不变
  • 下次打印时,新值会显示在棋盘上

如何遍历二维数组

遍历二维数组需要使用嵌套循环,但有两种不同的遍历顺序:行优先遍历和列优先遍历。

方法一:行优先遍历(先遍历行,再遍历列)

这是最常用的遍历方式,按照行的顺序访问元素:先完成第0行的所有列,再完成第1行的所有列,以此类推。

加载代码编辑器...

🔍 理解行优先遍历的执行过程

嵌套循环的工作原理:

  • 外层循环每执行一次,内层循环会完整地执行一遍
  • 内层循环结束后,外层循环才继续下一次迭代

详细执行步骤:

第1轮:外层循环 row=0

  • 进入外层循环第1次:row=0
  • 启动内层循环,完整遍历第0行:
    • col=0:访问 matrix[0][0] = 1
    • col=1:访问 matrix[0][1] = 2
    • col=2:访问 matrix[0][2] = 3
  • 内层循环结束

第2轮:外层循环 row=1

  • 进入外层循环第2次:row=1
  • 启动内层循环,完整遍历第1行:
    • col=0:访问 matrix[1][0] = 4
    • col=1:访问 matrix[1][1] = 5
    • col=2:访问 matrix[1][2] = 6
  • 内层循环结束

第3轮:外层循环 row=2

  • 进入外层循环第3次:row=2
  • 启动内层循环,完整遍历第2行:
    • col=0:访问 matrix[2][0] = 7
    • col=1:访问 matrix[2][1] = 8
    • col=2:访问 matrix[2][2] = 9
  • 内层循环结束

完整的访问顺序:

  1. matrix[0][0] = 1 (第1行第1列)
  2. matrix[0][1] = 2 (第1行第2列)
  3. matrix[0][2] = 3 (第1行第3列)
  4. matrix[1][0] = 4 (第2行第1列)
  5. matrix[1][1] = 5 (第2行第2列)
  6. matrix[1][2] = 6 (第2行第3列)
  7. matrix[2][0] = 7 (第3行第1列)
  8. matrix[2][1] = 8 (第3行第2列)
  9. matrix[2][2] = 9 (第3行第3列)

方法二:列优先遍历(先遍历列,再遍历行)

这种遍历方式按照列的顺序访问元素:先完成第0列的所有行,再完成第1列的所有行,以此类推。

加载代码编辑器...

列优先遍历的访问顺序:

  • 第0列:matrix[0][0]=1 → matrix[1][0]=4 → matrix[2][0]=7
  • 第1列:matrix[0][1]=2 → matrix[1][1]=5 → matrix[2][1]=8
  • 第2列:matrix[0][2]=3 → matrix[1][2]=6 → matrix[2][2]=9

两种遍历方式的对比

特点行优先遍历列优先遍历
循环顺序外层行,内层列外层列,内层行
访问顺序1→2→3→4→5→6→7→8→91→4→7→2→5→8→3→6→9
适用场景大多数情况,按行处理按列处理,垂直方向分析
使用频率常用较少,特殊需求
直观程度更直观,符合阅读习惯不太直观,需要转换思维

什么时候使用哪种遍历?

  • 行优先遍历:99%的情况下使用

    • 打印表格、矩阵
    • 逐行处理数据
    • 一般的数组操作
  • 列优先遍历:特殊情况下使用

    • 需要按列分析数据(如求每列的平均值)
    • 矩阵转置操作
    • 某些数学计算

动态创建数组

🔧 动态创建二维数组

除了手动创建二维数组,我们还可以通过程序动态生成。这在需要创建规律性数据结构时特别有用。

动态生成游戏地图

🎲 概率分布逻辑: 我们将使用 Math.random() 来生成随机游戏地图。Math.random() 会生成 0 到 1 之间的随机数:

  • 0.0 到 0.6 (60%的概率) → 空地 ⬜
  • 0.6 到 0.85 (25%的概率) → 墙 🧱
  • 0.85 到 1.0 (15%的概率) → 宝箱 💎

这样设置可以让地图中大部分是空地,适量的墙,少量的宝箱,游戏体验更平衡。

加载代码编辑器...

📝 核心要点总结

🔑 二维数组核心技能

  1. 创建二维数组:

    • 手动创建:let matrix = [[1, 2], [3, 4]];
    • 动态创建:使用嵌套循环逐行构建
  2. 访问元素:array[row][col]

    • 行索引从0开始
    • 列索引从0开始
    • 超出范围返回 undefined
  3. 遍历二维数组:

    • 按行遍历:外层循环控制行,内层循环控制列(常用方式)
    • 按列遍历:外层循环控制列,内层循环控制行(特殊需求)
    • 嵌套循环顺序决定数据访问模式
  4. 修改元素:array[row][col] = newValue

你已经完成了数组的深入学习!🎉 从一维数组的基础操作到二维数组的高级应用,你已经具备了处理复杂数据结构的能力。

Previous lesson
Previous
数组操作方法 - 添加、删除和管理
Next
引用概念 - 变量的不同行为
Next lesson