Appearance
2. 点名器(数组)
知识点:数组
项目:给全班 50 个同学做一个随机点名器
故事开场:老师说"开始",程序来点名
新学期,老师说:"以后每次上课,我们先玩一个游戏——随机点名。 被点到的同学,要站起来回答一个问题。"
可是老师太忙了,就想请你帮忙写一个小程序:程序里存着全班同学的名字, 每次运行,随机喊出一个同学。
听起来很简单,对吧?那我们先试试"第一个想到的办法"。
笨办法先行:50 个变量?
你大概想到了:存名字,不就是变量嘛!
ts
let name1 = "小明"
let name2 = "小红"
let name3 = "小刚"
// ……(还要写 47 个)
let name50 = "小雨"写名字倒还好,可"随机点名"怎么写?
ts
const number = Math.floor(Math.random() * 50) + 1 // 随机 1~50
let picked = ""
if (number === 1) {
picked = name1
} else if (number === 2) {
picked = name2
} else if (number === 3) {
picked = name3
} else if (number === 4) {
picked = name4
}
// ……(还要写 46 个 else if)你发现问题了吗?
- 写 50 个变量,已经够烦了
- 写 50 个
else if,简直是噩梦 - 要是明天转学来一个同学,变成 51 个,所有的数字都要改!
老师看出了你的难处,笑着说:"如果我给你一个编号的格子柜呢?"
引出知识点:数组就是编号的格子柜
想象教室后排有一个柜子,柜门上是 1、2、3……50 个编号, 每个格子里放一个同学的名字卡片。
- 柜子整体,就是一个数组(array):
names - 每个格子,叫元素(element)
- 格子的编号,叫下标(index)
在 TypeScript 里,数组长这样:
ts
const names = ["小明", "小红", "小刚"]注意!计算机数数和我们不一样:计算机从 0 开始数。
| 下标 | 0 | 1 | 2 |
|---|---|---|---|
| 名字 | 小明 | 小红 | 小刚 |
所以:
names[0]是"小明"names[1]是"小红"names[2]是"小刚"names.length是数组的长度,这里是 3
为什么从 0 开始? 这是个好问题——你可以把它想成"第 0 号格子离门口最近", 计算机要找到第 n 个格子,走 n 步就到了。以后学到指针和地址的时候, 你会发现从 0 开始是最自然的数法。
回到点名器。有了数组,一切都变简单了:
- 随机点名:
names[随机数字]——一个方括号搞定,不用写 50 个else if! - 新同学来了:只要往数组里加一个名字,其他代码一行都不用改
动手实现:点名器
把全班同学的名字放进去(这里先放 8 个,剩下的你可以自己加):
ts
const names = [
"小明", "小红", "小刚", "小雨",
"小丽", "小华", "小军", "小雪",
]
// 随机点名:从 0 到 names.length 之间,随机选一个下标
const index = Math.floor(Math.random() * names.length)
console.log("今天的幸运同学是:" + names[index])运行几次,看看是不是每次都不一样?
Math.random() 会返回一个 0 到 1 之间的随机小数(比如 0.437), 乘上 names.length(8)得到 0 到 8 之间的小数,Math.floor 把它向下取整, 正好得到 0~7 的整数——也就是数组的下标。这招要记住,以后天天用。
跑起来:把全班都点一遍
光点一个不过瘾。我们把整个班"点一遍"——用循环遍历数组:
ts
const names = [
"小明", "小红", "小刚", "小雨",
"小丽", "小华", "小军", "小雪",
]
// 全班点名:从第 0 个到最后一个,一个一个喊
for (let i = 0; i < names.length; i++) {
console.log("第 " + (i + 1) + " 号:" + names[i])
}
console.log("全班一共 " + names.length + " 人")输出:
第 1 号:小明
第 2 号:小红
第 3 号:小刚
第 4 号:小雨
第 5 号:小丽
第 6 号:小华
第 7 号:小军
第 8 号:小雪
全班一共 8 人看,for 里的 i 从 0 一直走到 names.length - 1,每一个名字都被喊到了。 这就是遍历数组——以后你会在无数程序里见到这个模式。
小挑战
- 报数:把上面的循环改一下,让程序从最后一个人开始往前点名 (提示:
i可以从names.length - 1开始,一路减到 0)。 - 找最长的名字:写一个循环,找出全班名字里字数最多的那个 (提示:
"小明".length会告诉你这个名字有几个字)。 - 升级点名器:让点名器"点过的同学不再点"(提示:可以把点过的名字 从数组里删掉——自己查一查
splice是怎么用的,或者用你自己的办法)。
课后练习
第 1 题(动手题):你的班级
把 names 数组换成你们班真实的同学名字(至少 5 个), 运行点名器,再运行全班点名,确认都能正常工作。
参考答案(点开查看)
把 names 里的名字改成你们班同学的名字,数量随便,其他代码不用改。 如果点名的次数比人数少,说明漏了人;比人数多,说明有重复——自己检查数组里 有没有写错的名字。
(这一步没有标准答案,程序跑得对,就是答案。)
第 2 题(思考题):越界了会怎样?
names 数组有 8 个元素,下标最大是 7。那么 names[8] 会是什么? names[100] 呢?运行看看,想想为什么。
参考答案(点开查看)
都是 undefined(没有定义)。
数组里没有 8 号、100 号格子,所以取出来的东西是"空的"。 程序不会报错——这其实很危险:你以为取到了值,其实拿到的是 undefined, 后面用它做运算时才会出问题。取数组元素前,先想想下标会不会越界。
第 3 题(思考题):数组能改吗?
我们说过,字符串 str[0] = "x" 是行不通的(字符串不能改,只能重新造)。 数组可以改吗?试试:
ts
const names = ["小明", "小红", "小刚"]
names[1] = "新同学"
console.log(names)运行看看,发生了什么?为什么数组可以改、字符串不可以?
参考答案(点开查看)
数组可以改:names 会变成 ["小明", "新同学", "小刚"]。
因为数组是"格子柜",格子里的卡片可以随时换;而字符串是一整串文字, TypeScript 不允许你"拆开改掉中间一个字",只能整个重新拼一个新的字符串。 这就是以后你会学到的两个词:可变(mutable)和不可变(immutable)。
下一课预告:你给好朋友写了一句"悄悄话",可是怕被同桌看到。 我们来发明一种只有你俩才懂的"密信"写法——顺便,你会发现字符串 和数组原来是亲戚。