上一单元你学会了定义函数,把"找敌人并攻击"打包成 findAndAttackEnemy()。但这种函数有个小遗憾:它每次只能自己在内部重新找敌人,外面没办法告诉它"这次请打这个敌人"。如果你已经在外面找好了一个特定目标,想交给函数去处理,无参函数就接不住了。
这正是本单元要解决的问题。关卡"边地之叉"开门见山:"函数通常需要参数。参数让函数具有更多灵活性,且不妨碍函数优化和简化代码的作用。"
参数(Parameter)就像函数的"输入口"——调用时把信息从这个口子递进去,函数就能针对你递进来的不同对象做同样的事。比如一个 checkAndAttack(target) 函数,这次递给它顶部的敌人就打顶部,下次递给它底部的敌人就打底部。一份代码,应付千变万化的目标。
| 对比 | 无参函数 | 带参函数 |
|---|---|---|
| 处理对象 | 固定(内部自己找) | 灵活(外部传进来) |
| 写法 | checkAndAttack() | checkAndAttack(target) |
| 灵活度 | 较低 | 高,一函数应万变 |
其实参数你早就用过了。"交给劈斩"关卡点破了真相:"在使用 hero.say('Hello!') 时,你将字符串 'Hello!' 作为参数传给了函数 say。" 同样,hero.attack(enemy) 里的 enemy、hero.moveXY(35, 34) 里的 35 和 34,都是参数。本单元的新本领是:给你自己定义的函数也加上参数。
关卡把两个关键词讲得很清楚:
| 术语 | 英文 | 在哪里 | 是什么 |
|---|---|---|---|
| 形参(形式参数) | Parameter | 函数定义时,括号里 | 一个预定义的变量,用来接住传入的信息 |
| 实参(实际参数) | Argument | 函数调用时,括号里 | 真正传进去的具体信息 |
用关卡原话总结:"实参(实际参数)是函数调用时 () 内包含的信息。形参(形式参数)则是传入函数的信息在函数内所使用的变量。"
"形参"听起来高深,其实关卡反复安慰你:"形式参数 'target' 只是一个变量!" 它和你之前用过的 enemy、distance 这些变量没有任何区别——可以在函数体里随意使用。唯一特别的是:它的值不是你赋的,而是调用时自动从外面"灌"进来的。
定义函数时,形参 target 是个贴好标签的空盒子。当你调用 checkAndAttack(topEnemy),系统就把 topEnemy 装进 target 这个盒子里。于是在函数内部,target 就代表着 topEnemy。下次调用 checkAndAttack(bottomEnemy),同一个盒子又装进了 bottomEnemy。
关卡强调:"被调用时的实际参数,在函数定义时同形式参数对应。" 也就是说,调用括号里写什么,函数定义括号里的形参就接住什么——它们按位置一一对接。
定义带参数的函数,只需在 def 的圆括号里写上形参的名字。然后在函数体里,就能像用普通变量一样使用它:
# 这个函数有一个形式参数 target def checkAndAttack(target): if target: # 把 target 当变量用 hero.attack(target) hero.moveXY(43, 34) # 还可加与形参无关的指令
调用时,把你准备好的具体值(实参)写进括号。典型流程是:先在外面找好目标存进变量,再把这个变量当实参传给函数。
hero.moveXY(58, 52) topEnemy = hero.findNearestEnemy() # 先找好目标 checkAndAttack(topEnemy) # 把 topEnemy 作为实参传入
这是最值得品味的一点。"交给劈斩"关卡讲得极妙:"enemy 是函数外敌人的名称,而 target 是函数内敌人的名称。这两个变量都指向同一个食人魔!"
def cleaveWhenClose(target): # 函数内:这个敌人叫 target if hero.distanceTo(target) < 5: hero.attack(target) enemy = hero.findNearestEnemy() # 函数外:同一个敌人叫 enemy cleaveWhenClose(enemy) # enemy 被传进去,变成 target
enemy 这个名字称呼,进了函数就改用 target 称呼。名字变了,指的还是同一个家伙。这就是参数"传递"的本质——传的是同一份信息,只是换了个内部代号。
关卡反复提醒:"target 可以像其他变量一样使用!" 你可以拿它去判断(if target:)、去攻击(hero.attack(target))、去测距离(hero.distanceTo(target)),甚至读取它的位置(target.pos.x)。它就是个普通变量,只不过值来自外部。
"捉迷藏"关卡指出:"你可以在函数内将形参作为变量使用。当然,你也可以添加与形参无关的额外指令。" 比如下面这个函数,先用形参 item 去捡东西,再做一件和 item 无关的事——回到营地中央躲起来:
def checkTakeHide(item): if item: hero.moveXY(item.pos.x, item.pos.y) # 用形参:去捡发光石 hero.moveXY(40, 34) # 额外指令:躲到营地中央
"捉迷藏"还强调:"同一个函数可以调用任意多次。" 这正是参数威力的集中体现——定义一次 checkTakeHide(item),在右上角、左上角、各个搜索点反复调用,每次传入当地搜到的发光石。一份逻辑,覆盖整张地图。
这一节聚焦每一关的策略、思路与算法设计,看懂"为什么这样传参",你就能自己写出完整解法。
这是你第一次定义带参数的函数。地图有上、下两个路口,难点在于意识到两处的逻辑完全一样,只是目标敌人不同——这正是参数的用武之地。算法思路是:把"如果目标存在就攻击,然后回防"封进 checkAndAttack(target),主循环里走到上路口找好 topEnemy 传进去,再走到下路口找好 bottomEnemy 传进去。
这一关把参数和 if/else 结合得很巧。任务是边防御食人魔、边开墓门。算法思路是写一个 checkToDefend(target):如果 target 存在就攻击它,否则(没敌人时)就去攻击 "Door"。换句话说,函数根据传入参数是否为空,自动在"打敌人"和"砸门"之间切换。
def checkToDefend(target): if target: hero.attack(target) # 有敌人 → 打敌人 else: hero.attack("Door") # 没敌人 → 砸门(注意用字符串)
attack 既能接收一个敌人对象(target),也能接收一个字符串名字("Door")。让函数在"没目标"时去做另一件有意义的事,是高效利用每一次循环的好策略。
这一关的战术核心是"打了就跑":出口被堵,还有骷髅怪在游荡,不能恋战。算法思路是写一个 hitOrHide(target):如果 target 存在,就攻击它,然后立刻移动到红色安全标记。攻击和走位被绑成一个动作,确保英雄出手后马上脱离危险。
target 负责"攻击谁",而紧随其后的 moveXY 是与参数无关的固定走位。把"攻击 + 撤退"打包,一次调用就完成一轮安全输出。
任务是收集 4 颗发光石,且每次捡到后都要躲回营地中央。算法思路是:定义 checkTakeHide(item)——若 item 存在,先移动到它的位置捡起,再回到中央 (40, 34) 躲藏。主循环里,英雄逐个走到各 X 标记,在每处 findNearestItem() 搜一颗石头,把结果作为实参传给同一个函数。
findNearestItem() 并把新结果传进去。这样同一个函数被反复调用、每次处理不同的石头,完美诠释了"一个函数调用多次"的复用思想。
这一关要保护矿工。算法巧思在于:只在没有敌人时才呼叫农民,否则贸然喊话会暴露并引来攻击。思路是写 checkEnemyOrSafe(target):如果 target 存在就攻击,否则用 hero.say("All clear!") 喊农民开工。和"盗墓者"如出一辙——根据参数是否为空,在两种行为间分流。
findNearestEnemy() 存入变量 → 把变量传给 checkEnemyOrSafe。改坐标、换变量名即可。
这一关把前几个单元的本领全串了起来。算法思路是写 cleaveWhenClose(target):先用 hero.distanceTo(target) 判断目标是否靠近(小于 5 米),近了再嵌套判断 isReady("cleave")——技能好了就 cleave,没好就普通 attack(target)。参数、距离比较、嵌套 if/else、技能冷却,四样齐上阵。
def cleaveWhenClose(target): if hero.distanceTo(target) < 5: # 用形参测距 if hero.isReady("cleave"): hero.cleave() else: hero.attack(target) # 还是用形参攻击
if enemy: 确认敌人存在再 cleaveWhenClose(enemy)。否则把空值传进去测距离会出错。
这是概念挑战,要求自己定义一个带参数的函数。战术核心是:只打靠近的食人魔,放过远处的投矛手——因为去碰远处的投矛手会引来一大群敌人。算法思路与"交给劈斩"几乎一致:cleaveWhenClose(target) 用 distanceTo 把距离当成"过滤器",只有近到一定范围(如 5 米内)的目标才出手,并优先用 cleave 防止敌人聚团。
target,函数体里却写成了 enemy。if 检查就传,导致测距离、攻击时出错。hero.——调用 cleaveWhenClose(enemy) 不要写 hero.。恭喜你完成边际森林第七单元!你的函数从"只会干固定的活"升级到了"听从指挥、灵活处理不同目标"。这是迈向真正模块化编程的关键一步。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 参数 | 给函数传信息,带来灵活性 | 全部 |
| 形参 Parameter | 定义时括号里的变量 | 边地之叉 |
| 实参 Argument | 调用时括号里的具体信息 | 边地之叉 |
| 形参就是变量 | 可像普通变量一样使用 | 盗墓者 |
| 同一对象两个名 | 外 enemy,内 target | 交给劈斩 |
| 额外指令 | 函数里可做与形参无关的事 | 墓地阴魂、捉迷藏 |
| 多次调用 | 一函数处理多个目标 | 捉迷藏、森林矿工 |
| 参数 + if/else | 按参数是否为空分流 | 盗墓者、森林矿工 |
| 参数 + 距离过滤 | 用距离筛选该不该打 | 交给劈斩、林地陷阱 |
会传参、懂复用的英雄,才是真正灵活机变的英雄。
继续守护边际森林吧,勇敢的程序员!🌲🛡️🎯
CodeCombat 编程知识 · 边际森林:第七单元
CodeCombat教研团队