Skip to content

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 开始数

下标012
名字小明小红小刚

所以:

  • 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,每一个名字都被喊到了。 这就是遍历数组——以后你会在无数程序里见到这个模式。


小挑战

  1. 报数:把上面的循环改一下,让程序从最后一个人开始往前点名 (提示:i 可以从 names.length - 1 开始,一路减到 0)。
  2. 找最长的名字:写一个循环,找出全班名字里字数最多的那个 (提示:"小明".length 会告诉你这个名字有几个字)。
  3. 升级点名器:让点名器"点过的同学不再点"(提示:可以把点过的名字 从数组里删掉——自己查一查 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)


下一课预告:你给好朋友写了一句"悄悄话",可是怕被同桌看到。 我们来发明一种只有你俩才懂的"密信"写法——顺便,你会发现字符串 和数组原来是亲戚。