第 6 课 · 第三部分 基础语法

数组与字典:一次装很多数据

变量只能装一个值。要装一列数据,就得用这两种"容器"。

学习目标

预计用时:读课 35 分钟 + 上机 25 分钟 |  前置知识:第 3 课(变量与数据类型)、第 5 课(循环,尤其是 For Each) |  本课性质:语法课(基础语法收官),概念与上机并重,是从"单个值"迈向"一批数据"的转折点

知识地图

前三部分是"认识工具"(第 1–2 课)和"基础语法"(第 3–6 课)。前面的变量只能装一个值;本课教你把一批值装进同一个容器,再用循环批量加工。它是第 7 课 Excel 自动化的地基——读取 Excel 得到的就是这里说的数组和数据表。

本课在哪儿
第三部分 · 基础语法。它是语法课的最后一节,也是第四部分"办公自动化"的起点。
重点
下标从 0 起;数组取数用整数、字典取数用字符串键;For Each 遍历;分割字符串。
难点
"别名"现象——两个变量名指向同一份数据;以及二维数组的行列读法。
学完的标志
给你一列用符号隔开的文本,你能把它切成数组、遍历加工、再算出汇总值,并且能解释改动副本为什么会影响原数组。

一、数组:一排带编号的格子

1.1 从"一个格子"到"一排格子"

第 3 课里,变量像一个格子:Dim 姓名 = "张三",一个名字对应一个值。 可现实里数据常常是一整列——五个月份的天数、一个班的学生名单。总不能起五个变量名 天数1、天数2……那样既啰嗦,又没法用循环批量处理。

数组就是把这些格子并排钉在一起,再从左到右编号。用方括号写出来: [1]

数组
Dim 天数 = [31, 28, 31, 30, 31]
31天数[0]
28天数[1]
31天数[2]
30天数[3]
31天数[4]

为什么需要数组?有了"一排格子 + 编号",才能做两件单独变量做不到的事:一是用编号(下标)按位置取数,比如"取第 3 个";二是用循环从头到尾扫一遍,代码长度不随数据条数增长。数据有一百条,循环还是那三行。

1.2 下标从 0 开始

头号易错点:数组的下标从 0 开始。第一个元素是 天数[0],不是 天数[1]。上例长度是 5,最大下标是 4。

为什么偏偏从 0 起?可以这样记:下标其实是"从第一个格子出发,往右走了几步"。第一个格子还没走,所以是 0; 第二个格子走了一步,所以是 1。第 N 个元素的下标 = N − 1,最后一个元素的下标 = 长度 − 1。

你想要下标取值
第 1 个元素0天数[0] → 31
第 3 个元素2天数[2] → 31
最后一个元素长度 − 1 = 4天数[4] → 31

1.3 混合类型与二维数组

同一个数组里的元素类型可以混着放,也可以再套一个数组(这就是"二维数组"):

混合与嵌套
Dim 混合 = [1, "小明", True]
Dim 表格 = [["张三", "语文"], ["李四", "数学"]]
输出调试信息(表格[1][0])      // 李四

二维数组是"数组里装的还是数组",可以想成一张表格:外层是行,里层是列。 取数的口诀是先定行、再定列:表格[1][0] 的第一个 [1] 取第 2 行(从 0 起),第二个 [0] 取这一行的第 1 列,结果是 "李四"。

写法行(从 0)列(从 0)结果
表格[0][0]第 1 行第 1 列张三
表格[0][1]第 1 行第 2 列语文
表格[1][0]第 2 行第 1 列李四

易错点(维数不匹配):数组维数在定义时就定死了。把一维数组当二维用(写成 混合[0][0]),或者反过来,都会出错。定义时是几层方括号,取值时就要写几组下标。这条到第 7 课写 Excel 时尤其致命。

二、字典:按名字取东西

字典像通讯录——用名字而不是编号来取。[1]

字典
Dim 学生 = { "姓名": "张三", "年龄": "26", "城市": "北京" }
输出调试信息(学生["姓名"])      // 张三
学生["年龄"] = "30"            // 直接改
学生["性别"] = "男"            // 直接加,字典里就多一项

规则:字典的"名字"(键)只能是字符串;"值"可以是任意类型。取元素时,数组用整数下标,字典用字符串键。

为什么用字典?数组靠"第几个"定位,可你常常只记得数据的含义(姓名、年龄),记不住它是第几个。字典把"含义"直接当钥匙:学生["姓名"] 比 学生[0] 好读得多,也不怕中途插入一项导致序号全变。

对比项数组 [ ]字典 { }
用什么取整数下标(从 0 起)字符串键
顺序有固定先后按名字找,不靠顺序
典型例子一排天数、一列姓名一条记录的各个字段

可增可改:给一个已存在的键赋值就是"改",给一个不存在的键赋值就是"加"——两种写法完全一样,都是 字典["键"] = 值。键必须是字符串,所以别写成 学生[姓名](那样会把 姓名 当成变量)。

数组用从 0 开始的整数下标取值;字典用字符串键取值 数组用编号找,字典用名字找 一排格子靠位置;一条记录靠含义。 数组 [ ] 0 1 2 3 4 31 28 31 30 31 第一个 最后一个 下标从 0 开始;长度 5 的最后一个下标是 4。 天数[5] 会越界报错 一排格子,靠位置定位。 字典 { } 姓名 张三 年龄 26 城市 北京 键只能是字符串;值可以是任意类型。 一条记录,靠含义取值。
图 4 · 数组用整数下标(从 0 开始),字典用字符串键;两者都能用 For Each 逐个遍历。

三、怎么遍历它们:For Each

第 5 课学的遍历循环,正是为这两种容器准备的:[1]

遍历数组 / 遍历字典
Dim 天数 = [31, 28, 31]
Dim 合计 = 0
For Each 天 In 天数
    合计 = 合计 + 天
Next

For Each 键, 值 In 学生        // 字典:同时拿名字和值
    输出调试信息(键 & "=" & 值)
Next

遍历数组时,循环变量(上例的 天)每一轮自动换成下一个元素;遍历字典时,For Each 键, 值 形式可以同时拿到"名字"和"值"。 关键优点:你完全不用自己管下标,自然就不会写错"从 0 还是从 1 起",也不会越界。

小技巧:如果确实需要知道"现在是第几个",可以在循环外先放一个计数变量 Dim i = 0,循环体内处理完再 i = i + 1。用 For Each 管数据、用计数器管序号,两件事分开想,不容易乱。

四、在可视化视图里操作数组/字典

命令区里有一整个 数据处理 分类,下面是认证常考的几条:[2]

分类命令作用
数组在数组尾部添加元素往数组末尾追加一个元素
数组过滤数组数据按条件保留或剔除元素
字符串分割字符串按分隔符把一段文字切成数组

在可视化视图里用一条数据处理命令,流程都差不多:

  1. 在命令区找到分类(数组类命令在 数据处理 ▸ 数组,分割字符串在 数据处理 ▸ 字符串)。
  2. 把它拖到流程里要执行的位置。
  3. 选中这条命令,在右侧属性区填:要处理的原数组/原文本、条件或要追加的元素、以及"输出到"哪个变量。
  4. 用 输出调试信息 把输出变量打出来,运行一遍,看结果对不对。

说明:每条命令属性区里的字段名称,以你本机命令面板实际显示的为准;本课只讲"要填哪几类东西"以及结果长什么样,不背字段名。

特别好用的一条:分割字符串。把 "zhangsan|lisi|wangwu" 按 | 分割,得到数组 ["zhangsan","lisi","wangwu"]。CSV、日志这类"用符号隔开"的文本,全靠它变成数组再逐条处理。[2]

五、两个必须知道的坑

坑 1:赋值 = 取别名,不是复制

把数组/字典赋给另一个变量,只是给它起了个别名,两者指向同一份数据。改一个,另一个跟着变:[1]

别名陷阱
a = [10, 20, 30]
b = a              // b 只是 a 的别名,不是副本
b[1] = 99          // 改 b
输出调试信息(a)     // a 也变成了 [10, 99, 30]!

为什么会这样?看图就懂了。执行 b = a 时,并没有搬出一排新格子;只是把 a、b 两个名字贴在了同一排格子上:

10a[0] · b[0]
20a[1] · b[1]
30a[2] · b[2]

所以 b[1] = 99 改的是这排格子的第 2 格,a 再看过去当然也变了:

10a[0] · b[0]
99a[1] · b[1]
30a[2] · b[2]

想要真正独立的一份,要用数据处理里的复制相关命令,不能直接赋值。

易错点(别名):基础类型(数字、字符串、布尔)用 = 是真复制,改副本不影响原值;数组和字典是取别名,改副本会连带改原值。考试问"两个变量什么关系",认准"指向同一份数据"这个说法。备份数组/字典,一律走复制命令。

坑 2:下标越界会报错

数组只有 5 个元素却去取 天数[5],运行时会直接报错停下。遍历时用 For Each 就不会踩这个坑,因为它是"有多少取多少"。

易错点(越界):长度 5 的数组,合法下标是 0 到 4,5 就已经在格子外面了。手工写循环 For i = 0 To 5 很容易多走一步——要么写成 To 4,要么干脆用 For Each。

六、预告:数据表——Excel 的接口

官方还有一种特殊容器叫数据表(DataTable),可以理解成"带表头的二维数组",能排序、能筛选: 构建数据表 用一维数组做表头、二维数组做数据。[2] 下一部分读 Excel 时,读出来的通常就是数据表——所以这一课是 Excel 的地基。

承上启下:到这里,基础语法全部学完:变量装单值、数组装一排、字典装一条记录、循环批量加工、条件做判断。下一课把它们接上 Excel——用数组承接读出来的表格数据,用 For Each 逐行处理,用写入命令回填。

Python 写法对照:这一课的写法几乎一样——下标从 0 开始、字典用键取值,两种语言完全一致。 真正的差别只在长度和遍历上。

要做的BotScript(本课程主线)Python
定义数组Dim 天数 = [31, 28, 31]天数 = [31, 28, 31](一样)
按下标取元素天数[0]天数[0](一样,都从 0 起)
二维数组表格[1][0]表格[1][0](一样)
定义字典Dim 学生 = { "姓名": "张三" }学生 = {"姓名": "张三"}(一样)
取 / 改 / 新增键学生["年龄"] = "30"学生["年龄"] = "30"(一样)
遍历数组For Each 天 In 天数for 天 in 天数:
遍历字典For Each 键, 值 In 学生for 键, 值 in 学生.items():
取长度本课没有引入取长度的命令,直接用 For Each 就能避开越界len(天数)
复制一份数组b = a 只是别名,不是副本b = a 也只是别名;要真副本得写 b = list(a)

最后一行的别名陷阱,两种语言一模一样——本课讲的那个坑(改 b 结果 a 也变了) 在 Python 里同样会发生。所以你学到的不是"BotScript 的怪癖",而是这类数据结构共有的性质: 变量装的是"指向那排格子的地址",不是格子本身。Python 里要真复制,用 list(a)。

本课小结

必记要点

数组 一排带编号的格子 下标 格子编号,从 0 起 二维数组 数组套数组 = 表格 字典 按名字取值 键 字典的名字,只能是字符串 For Each 逐个遍历容器 别名 两个名字指同一份数据 数据表 带表头的二维数组

自测

不看上面,凭记忆作答。答错不要紧,解析会告诉你为什么。

上机实践(回到 Windows)

任务 A(求总和):定义数组 [31,28,31,30,31],用 For Each 求总和(应为 151),输出结果。

任务 B(字典):定义字典 {"姓名":"张三","年龄":"26"},输出"姓名"对应的值;再往里加一个键"性别",输出整个字典,观察它长了什么样。

任务 C(文本转数组):用 分割字符串 命令,把 "a,b,c,d" 按 , 切分,输出得到的数组,确认它是 ["a","b","c","d"]。

任务 D(验证坑 1):照上面"别名陷阱"那段代码跑一遍,确认 a 真的被改掉了。把这个现象发我,说说你打算怎么避免它。

任务 E(故意制造报错):先定义 Dim 天数 = [31, 28, 31](长度 3),然后故意输出 天数[3]。运行,把报错原文抄给我;再把同一段逻辑改成 For Each 遍历重跑,确认不再报错。

任何一步卡住,把命令属性截图 + 报错原文发给我。

精读一手来源

本节首选:《开发者指南(第二辑)· 数据获取和处理》的"数组""数据表"两节——它给了每条命令的属性配置和运行结果。 语法层面看 《BotScript 语言参考 · 复合类型》。 语法速查见 参考页:BotScript 语法速查(已补入数组/字典一节)。