上一单元你学会了给函数加一个参数,让它能处理不同的目标。但现实里,很多任务需要同时告诉函数好几件事。比如"去某个坐标建陷阱",函数光知道"建什么"还不够,还得知道"建在哪儿"——这就需要 x 和 y 两个坐标。一个参数装不下这么多信息。
"返回荆棘农场"关卡开门见山地点破:"你的函数可以定义不止一个参数。" 这正是本单元的核心本领——让你自己定义的函数接收多个参数,从而一次性接收"做什么 + 在哪里 + 怎么做"的全部指令。
关卡用一个你早就熟悉的例子安慰你:"就像 moveXY 接受两个参数那样,你创建的函数也可以定义不止一个参数!" 仔细想想,hero.moveXY(35, 34) 里的 35 和 34 就是两个参数;而 hero.buildXY("fire-trap", 40, 20) 更厉害,它有三个参数:一个字符串和两个数字。你一直在使用多参数函数,现在只是要自己来定义它们。
| 内置函数 | 参数个数 | 参数类型 |
|---|---|---|
hero.moveXY(x, y) | 2 个 | 数字, 数字 |
hero.buildXY(type, x, y) | 3 个 | 字符串, 数字, 数字 |
hero.attack(enemy) | 1 个 | 对象 |
关卡再次强调了上单元的关键区分:"形参(parameter)是在函数定义里的参数。实参(argument)是函数被调用时传进的实际参数值!" 在多参数时代,这个区分依然成立,只不过现在每个位置都有一对形参/实参的对应关系。
| 术语 | 英文 | 出现位置 | 含义 |
|---|---|---|---|
| 形参 | Parameter | 函数定义的括号里 | 一串用逗号分隔的变量名 |
| 实参 | Argument | 函数调用的括号里 | 一串用逗号分隔的具体值 |
定义多参数函数,只需在 def 的括号里写下多个形参名,用逗号隔开。然后在函数体里,这几个形参都能像普通变量一样使用:
# 函数 maybeBuildTrap 定义了两个参数 x 和 y! def maybeBuildTrap(x, y): hero.moveXY(x, y) # 用 x、y 作为移动坐标 enemy = hero.findNearestEnemy() if enemy: hero.buildXY("fire-trap", x, y) # 再用 x、y 作为建造坐标
定义 maybeBuildTrap(x, y) 时,相当于准备了两个贴好标签的空盒子:第一个叫 x,第二个叫 y。当你调用 maybeBuildTrap(43, 50),系统会按顺序把 43 装进 x,把 50 装进 y。盒子的顺序不能乱,否则装错了东西。
"封闭的十字路口"关卡郑重提醒:"调用函数时,参数顺序需要同定义时保持一致。" 这是多参数最关键、也最容易出错的规则。第一个实参对应第一个形参,第二个对应第二个……严格按位置一一对接。
maybeBuildSomething(40, "fire-trap", 20) 这样写,函数会以为 40 是要建造的"类型"、"fire-trap" 是 x 坐标——完全乱套,程序会出错。
关卡明确说明:"函数可以定义很多参数,类型也可以各异,诸如字符串、数字等。" 以 buildXY 为例,它的三个参数里 typeToBuild 是字符串,x 和 y 是数字。同一个函数的不同形参,可以承载完全不同种类的信息。
最经典的多参数用法,就是把 x、y 坐标作为参数传入。这样同一个函数能被带去地图上的任何位置执行任务。定义一次"去某点干活"的逻辑,就能在所有入口、所有标记处反复调用,每次只换坐标。
def maybeBuildTrap(x, y): hero.moveXY(x, y) enemy = hero.findNearestEnemy() if enemy: hero.buildXY("fire-trap", x, y) while True: maybeBuildTrap(43, 50) # 上方入口 maybeBuildTrap(25, 34) # 左侧入口 maybeBuildTrap(43, 20) # 底部入口
x、y 在函数里被用了两次——先用于 moveXY 移动,再用于 buildXY 建造。同一对坐标,既决定"走到哪",又决定"建在哪"。这种复用让代码格外紧凑。
当你把"建造类型"也变成参数,函数就更万能了。"封闭的十字路口"用 maybeBuildSomething(typeToBuild, x, y) 把建造物类型连同坐标一起参数化——同一个函数,既能造 "fence" 又能造 "fire-trap"。关卡点评道:"现在你可以为 'fence' 和 'fire-trap' 使用同一个函数,而无需两个不同函数,这样代码就会简洁很多!"
def maybeBuildSomething(typeToBuild, x, y): hero.moveXY(x, y) enemy = hero.findNearestEnemy() if enemy: hero.buildXY(typeToBuild, x, y) # 类型也来自参数
多参数函数还有一项重要用途:重构(Refactoring)。当代码里塞满了嵌套的 if 语句、显得又长又乱时,可以把最内层的核心逻辑抽取出来,封装成一个带参数的函数,主循环里只用一行调用它。代码立刻变得清爽易读。
"Agrippa 重构"关卡正是教这个:原本一大堆嵌套判断里"该劈斩还是该普攻"的逻辑,被抽进 cleaveOrAttack(enemy) 函数,主循环只需在合适时机调用它一次。关卡总结:"函数能够帮你简化代码,使代码更易读。"
这一节聚焦每一关的策略、思路与算法设计。看懂"为什么要这样设计参数",你就能自己写出完整解法。
这是你第一次定义带两个参数的函数。农场有多个入口,食人魔可能从任意一个冲进来。难点在于意识到每个入口的处理逻辑完全相同——走过去、看有没有敌人、有就建陷阱——只是坐标不同。算法思路是:把这套逻辑封进 maybeBuildTrap(x, y),主循环里依次传入各入口坐标即可。
(20, 34)、(38, 20)、(56, 34) 等)。函数体一字不改,只换调用时的实参——这正体现了多参数函数"一次定义、处处复用"的价值。
这一关的精髓不是打法,而是代码组织。原本的解法有很多层嵌套 if,读起来费劲。重构的思路是:找出最内层那段"判断技能冷却、决定劈斩还是普攻"的代码,把它抽成 cleaveOrAttack(enemy) 函数。主循环负责"找敌人、判断距离",到了出手环节只需一行 cleaveOrAttack(enemy)。
def cleaveOrAttack(enemy): if hero.isReady("cleave"): hero.cleave(enemy) # 技能好了 → 劈斩 else: hero.attack(enemy) # 否则 → 普攻
这一关把参数升级到三个,而且类型不同。任务要求顺时针绕村庄移动,在上下通道建 "fire-trap"、在左右通道建 "fence"。算法巧思在于:不为两种建造物各写一个函数,而是把"建什么"也作为参数。于是 maybeBuildSomething(typeToBuild, x, y) 一个函数通吃,调用时传入不同的类型字符串和坐标即可。
这一关需要自己从零编写多参数函数,没有现成范例。战术核心很巧妙:不收集硬币,而是把硬币当作"诱饵",只在看到硬币时才在该处建火焰陷阱(因为农民也会游荡,不能见人就建)。算法思路是 maybeBuildTrap(x, y):移动到坐标 → findNearestItem() 找硬币 → 若有硬币就 buildXY("fire-trap", x, y)。主循环巡逻四个角的通道。
这是一道概念挑战,要求独立定义并使用带多个参数的函数。任务是在循环里移动到每个 X 标记,发现敌人就攻击。算法思路很直接:写 checkPoint(x, y)——移动到 (x, y) → 找最近敌人 → 有就攻击。主循环里把各个检查点坐标作为实参逐一传入。
def checkPoint(x, y): hero.moveXY(x, y) enemy = hero.findNearestEnemy() if enemy: hero.attack(enemy) while True: checkPoint(10, 30) checkPoint(30, 10) checkPoint(50, 30)
这是一道组合挑战,目标是把"定义函数 + 使用函数 + 带参数的函数"三项技能全部用上,巡逻所有 X 标记并击败食人魔。算法思路与"最后的大炮"一脉相承:定义 patrol(x, y) 函数完成"移动—找敌—攻击",主循环依次传入各标记坐标。一个干净的多参数函数,就能优雅地完成整张地图的巡逻。
maybeBuildSomething(40, "fence", 34) 写错了次序,函数会把数字当类型、字符串当坐标。def maybeBuildTrap(x y):,缺少逗号会导致语法错误。x, y,函数体里却写成了别的名字。"fire-trap"、"fence" 必须用引号且拼写准确。hero.——调用 checkPoint(10, 30) 不要写成 hero.checkPoint(...)。恭喜你完成边际森林第八单元!你的函数从"只能接收一份信息"升级到了"同时接收多份信息、处理复杂任务"。配合重构思想,你已经能写出既灵活又整洁的代码。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 多参数函数 | 括号里写多个形参,逗号分隔 | 返回荆棘农场 |
| 顺序对应 | 实参按位置一一对接形参 | 封闭的十字路口 |
| 类型各异 | 字符串、数字、对象都能当参数 | 封闭的十字路口 |
| 坐标参数 x, y | 一个函数走遍全图 | 返回荆棘农场、贪婪陷阱 |
| 类型参数化 | 同一函数造围栏也造陷阱 | 封闭的十字路口 |
| 重构 | 抽取内层逻辑、简化代码 | Agrippa 重构 |
| 形参当变量复用 | 同一参数在函数里可多次使用 | 返回荆棘农场 |
| 概念/组合挑战 | 独立设计多参数函数 | 最后的大炮、函数巡逻 |
会传多参、懂重构的英雄,才能驾驭真正复杂的战场。
继续守护边际森林吧,勇敢的程序员!🌲🛡️🎯
CodeCombat 编程知识 · 边际森林:第八单元
CodeCombat教研团队