第七单元:函数参数

Unit 7: Function Parameters & Arguments
边际森林 Border Forest · 边地之叉 → 林地陷阱

7.1 为什么需要参数

Why We Need Parameters

7.1.1 无参函数的局限(The Limits of Parameter-less Functions)

7.1.1.1 函数也想"听指挥" Functions want instructions too

上一单元你学会了定义函数,把"找敌人并攻击"打包成 findAndAttackEnemy()。但这种函数有个小遗憾:它每次只能自己在内部重新找敌人,外面没办法告诉它"这次请打这个敌人"。如果你已经在外面找好了一个特定目标,想交给函数去处理,无参函数就接不住了。

这正是本单元要解决的问题。关卡"边地之叉"开门见山:"函数通常需要参数。参数让函数具有更多灵活性,且不妨碍函数优化和简化代码的作用。"

7.1.2 参数带来灵活性(Parameters Bring Flexibility)

7.1.2.1 同一个函数,处理不同的对象 One function, different targets

参数(Parameter)就像函数的"输入口"——调用时把信息从这个口子递进去,函数就能针对你递进来的不同对象做同样的事。比如一个 checkAndAttack(target) 函数,这次递给它顶部的敌人就打顶部,下次递给它底部的敌人就打底部。一份代码,应付千变万化的目标。

对比无参函数带参函数
处理对象固定(内部自己找)灵活(外部传进来)
写法checkAndAttack()checkAndAttack(target)
灵活度较低高,一函数应万变

7.1.3 你早就在传参数(You've Been Passing Arguments)

7.1.3.1 say、attack 都吃参数 say and attack already take arguments

其实参数你早就用过了。"交给劈斩"关卡点破了真相:"在使用 hero.say('Hello!') 时,你将字符串 'Hello!' 作为参数传给了函数 say。" 同样,hero.attack(enemy) 里的 enemyhero.moveXY(35, 34) 里的 3534,都是参数。本单元的新本领是:给你自己定义的函数也加上参数。

7.2 形参与实参

Parameters vs. Arguments

7.2.1 两个核心概念(Two Key Concepts)

7.2.1.1 形参和实参的分工 The roles of parameters and arguments

CodeCombat 形参与实参示意图 - codecombat.cn
图1:实参在调用时传入,形参在函数内部接住——同一份信息的两个名字

关卡把两个关键词讲得很清楚:

术语英文在哪里是什么
形参(形式参数)Parameter函数定义时,括号里一个预定义的变量,用来接住传入的信息
实参(实际参数)Argument函数调用时,括号里真正传进去的具体信息

用关卡原话总结:"实参(实际参数)是函数调用时 () 内包含的信息。形参(形式参数)则是传入函数的信息在函数内所使用的变量。"

7.2.2 形参就是变量(A Parameter is Just a Variable)

7.2.2.1 别被"形参"吓到 Don't be scared by the word

"形参"听起来高深,其实关卡反复安慰你:"形式参数 'target' 只是一个变量!" 它和你之前用过的 enemydistance 这些变量没有任何区别——可以在函数体里随意使用。唯一特别的是:它的值不是你赋的,而是调用时自动从外面"灌"进来的

💡 形参像一个空盒子,等着被装满

定义函数时,形参 target 是个贴好标签的空盒子。当你调用 checkAndAttack(topEnemy),系统就把 topEnemy 装进 target 这个盒子里。于是在函数内部,target 就代表着 topEnemy。下次调用 checkAndAttack(bottomEnemy),同一个盒子又装进了 bottomEnemy

7.2.3 实参传给形参(Arguments Fill Parameters)

7.2.3.1 一一对应的"对接" A one-to-one match

关卡强调:"被调用时的实际参数,在函数定义时同形式参数对应。" 也就是说,调用括号里写什么,函数定义括号里的形参就接住什么——它们按位置一一对接。

checkAndAttack ( topEnemy ) ← 调用:传入实参 实参装进形参 def checkAndAttack ( target ): ← 定义:声明形参

7.3 参数语法详解

Parameter Syntax in Detail

7.3.1 定义带参函数(Defining with Parameters)

7.3.1.1 在括号里写下形参名 Write the parameter name in the parentheses

定义带参数的函数,只需在 def 的圆括号里写上形参的名字。然后在函数体里,就能像用普通变量一样使用它:

# 这个函数有一个形式参数 target
def checkAndAttack(target):
    if target:                 # 把 target 当变量用
        hero.attack(target)
    hero.moveXY(43, 34)    # 还可加与形参无关的指令

7.3.2 调用时传实参(Passing Arguments)

7.3.2.1 把准备好的信息递进去 Hand in the prepared information

调用时,把你准备好的具体值(实参)写进括号。典型流程是:先在外面找好目标存进变量,再把这个变量当实参传给函数。

hero.moveXY(58, 52)
topEnemy = hero.findNearestEnemy()   # 先找好目标
checkAndAttack(topEnemy)          # 把 topEnemy 作为实参传入

7.3.3 同一对象,两个名字(Same Object, Two Names)

7.3.3.1 外面叫 enemy,里面叫 target enemy outside, target inside

这是最值得品味的一点。"交给劈斩"关卡讲得极妙:"enemy 是函数外敌人的名称,而 target 是函数内敌人的名称。这两个变量都指向同一个食人魔!"

def cleaveWhenClose(target):      # 函数内:这个敌人叫 target
    if hero.distanceTo(target) < 5:
        hero.attack(target)

enemy = hero.findNearestEnemy()  # 函数外:同一个敌人叫 enemy
cleaveWhenClose(enemy)          # enemy 被传进去,变成 target
关键洞察:同一个食人魔,在函数外用 enemy 这个名字称呼,进了函数就改用 target 称呼。名字变了,指的还是同一个家伙。这就是参数"传递"的本质——传的是同一份信息,只是换了个内部代号。

7.4 用好参数

Making the Most of Parameters

7.4.1 形参当变量用(Use the Parameter as a Variable)

7.4.1.1 想怎么用就怎么用 Use it however you like

关卡反复提醒:"target 可以像其他变量一样使用!" 你可以拿它去判断(if target:)、去攻击(hero.attack(target))、去测距离(hero.distanceTo(target)),甚至读取它的位置(target.pos.x)。它就是个普通变量,只不过值来自外部。

7.4.2 加入额外指令(Adding Extra Instructions)

7.4.2.1 函数里不止能用形参 A function can do more than use its parameter

"捉迷藏"关卡指出:"你可以在函数内将形参作为变量使用。当然,你也可以添加与形参无关的额外指令。" 比如下面这个函数,先用形参 item 去捡东西,再做一件和 item 无关的事——回到营地中央躲起来:

def checkTakeHide(item):
    if item:
        hero.moveXY(item.pos.x, item.pos.y)  # 用形参:去捡发光石
        hero.moveXY(40, 34)               # 额外指令:躲到营地中央

7.4.3 一个函数,多次调用(Call It as Many Times as You Want)

7.4.3.1 复用,是参数的最大价值 Reuse is the greatest value

"捉迷藏"还强调:"同一个函数可以调用任意多次。" 这正是参数威力的集中体现——定义一次 checkTakeHide(item),在右上角、左上角、各个搜索点反复调用,每次传入当地搜到的发光石。一份逻辑,覆盖整张地图。

7.5 实战关卡精讲

Level Strategy & Analysis

这一节聚焦每一关的策略、思路与算法设计,看懂"为什么这样传参",你就能自己写出完整解法。

7.5.1 边地之叉(Backwoods Fork)

7.5.1.1 策略:一个函数守两个路口 Strategy: one function guards two forks

CodeCombat 边地之叉关卡 - codecombat.cn
图2:边地之叉——同一个 checkAndAttack 函数,分别处理上、下两路敌人

这是你第一次定义带参数的函数。地图有上、下两个路口,难点在于意识到两处的逻辑完全一样,只是目标敌人不同——这正是参数的用武之地。算法思路是:把"如果目标存在就攻击,然后回防"封进 checkAndAttack(target),主循环里走到上路口找好 topEnemy 传进去,再走到下路口找好 bottomEnemy 传进去。

思路要点:注意函数体最后那句"回到 (43, 34)"是与参数无关的额外指令——无论打没打到敌人,都要回防中点。这体现了"形参 + 固定动作"的组合设计。

7.5.2 盗墓者(Grave Robber)

7.5.2.1 策略:用 if/else 给参数"分流" Strategy: branch on the parameter with if/else

CodeCombat 盗墓者关卡 - codecombat.cn
图3:盗墓者——有敌人就打敌人,没敌人就专心砸门

这一关把参数和 if/else 结合得很巧。任务是边防御食人魔、边开墓门。算法思路是写一个 checkToDefend(target):如果 target 存在就攻击它,否则(没敌人时)就去攻击 "Door"。换句话说,函数根据传入参数是否为空,自动在"打敌人"和"砸门"之间切换。

def checkToDefend(target):
    if target:
        hero.attack(target)     # 有敌人 → 打敌人
    else:
        hero.attack("Door")   # 没敌人 → 砸门(注意用字符串)
算法妙处:attack 既能接收一个敌人对象(target),也能接收一个字符串名字("Door")。让函数在"没目标"时去做另一件有意义的事,是高效利用每一次循环的好策略。

7.5.3 墓地阴魂(Tomb of the Forgotten)

7.5.3.1 策略:打完就撤,攻击与走位结合 Strategy: hit and retreat

CodeCombat 墓地阴魂关卡 - codecombat.cn
图4:墓地阴魂——攻击一下立刻退回红色标记躲避骷髅怪

这一关的战术核心是"打了就跑":出口被堵,还有骷髅怪在游荡,不能恋战。算法思路是写一个 hitOrHide(target):如果 target 存在,就攻击它,然后立刻移动到红色安全标记。攻击和走位被绑成一个动作,确保英雄出手后马上脱离危险。

设计思想:这又是"形参 + 额外指令"的经典模式——形参 target 负责"攻击谁",而紧随其后的 moveXY 是与参数无关的固定走位。把"攻击 + 撤退"打包,一次调用就完成一轮安全输出。

7.5.4 捉迷藏(Hide and Seek)

7.5.4.1 策略:收集型任务的参数化 Strategy: parameterize a collection task

CodeCombat 捉迷藏关卡 - codecombat.cn
图5:捉迷藏——每到一个 X 标记搜发光石,捡到就躲回中央

任务是收集 4 颗发光石,且每次捡到后都要躲回营地中央。算法思路是:定义 checkTakeHide(item)——若 item 存在,先移动到它的位置捡起,再回到中央 (40, 34) 躲藏。主循环里,英雄逐个走到各 X 标记,在每处 findNearestItem() 搜一颗石头,把结果作为实参传给同一个函数。

关键算法点:每到一个新地点,都要重新 findNearestItem() 并把新结果传进去。这样同一个函数被反复调用、每次处理不同的石头,完美诠释了"一个函数调用多次"的复用思想。

7.5.5 森林矿工(Woodland Cleaver Mine)

7.5.5.1 策略:用参数判断"该打还是该喊话" Strategy: attack or call workers

CodeCombat 森林矿工关卡 - codecombat.cn
图6:森林矿工——安全时喊农民来挖矿,有敌人时先清剿

这一关要保护矿工。算法巧思在于:只在没有敌人时才呼叫农民,否则贸然喊话会暴露并引来攻击。思路是写 checkEnemyOrSafe(target):如果 target 存在就攻击,否则用 hero.say("All clear!") 喊农民开工。和"盗墓者"如出一辙——根据参数是否为空,在两种行为间分流。

举一反三:关卡只给了右上角 X 标记的范例代码,要你照着写左下角的。模式完全一致:移动 → findNearestEnemy() 存入变量 → 把变量传给 checkEnemyOrSafe。改坐标、换变量名即可。

7.5.6 交给劈斩(Leave It to Cleaver)

7.5.6.1 策略:参数 + 距离 + 技能三重判断 Strategy: parameter + distance + skill check

CodeCombat 交给劈斩关卡 - codecombat.cn
图7:交给劈斩——等敌人靠近,劈斩就绪时放大招,否则普攻

这一关把前几个单元的本领全串了起来。算法思路是写 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)。否则把空值传进去测距离会出错。

7.5.7 林地陷阱(概念挑战 · Forest Trap)

7.5.7.1 策略:用距离过滤"该不该打" Strategy: filter targets by distance

这是概念挑战,要求自己定义一个带参数的函数。战术核心是:只打靠近的食人魔,放过远处的投矛手——因为去碰远处的投矛手会引来一大群敌人。算法思路与"交给劈斩"几乎一致:cleaveWhenClose(target)distanceTo 把距离当成"过滤器",只有近到一定范围(如 5 米内)的目标才出手,并优先用 cleave 防止敌人聚团。

算法本质:关卡提示"确保不攻击投矛手的最简单办法是检查 distanceTo 小于特定数值"。这说明距离判断可以充当一道筛选条件——参数传进来后,先用它算距离,决定这个目标值不值得打。这是一种非常实用的目标筛选思路。

7.6 常见错误

Common Mistakes

7.6.1 新手最容易踩的坑(Pitfalls for Beginners)

7.6.1.1 参数环节的典型问题 Typical issues with parameters

CodeCombat 参数常见错误示意 - codecombat.cn
图8:忘记传实参、形参名写错,是参数环节最常见的失误
易错点清单:
  • 定义了形参却忘记传实参——调用时括号空着,函数收不到信息。
  • 形参名前后不一致——定义时叫 target,函数体里却写成了 enemy
  • 把空值当实参传入——没先 if 检查就传,导致测距离、攻击时出错。
  • 给自定义函数误加 hero.——调用 cleaveWhenClose(enemy) 不要写 hero.
  • 实参顺序错位——多个参数时,传入顺序要和形参顺序一一对应。
  • 误以为改形参就能改外部变量——形参只是同一对象的内部代号。

本单元小结 ✨

恭喜你完成边际森林第七单元!你的函数从"只会干固定的活"升级到了"听从指挥、灵活处理不同目标"。这是迈向真正模块化编程的关键一步。来回顾一下核心概念:

核心概念关键点对应关卡
参数给函数传信息,带来灵活性全部
形参 Parameter定义时括号里的变量边地之叉
实参 Argument调用时括号里的具体信息边地之叉
形参就是变量可像普通变量一样使用盗墓者
同一对象两个名外 enemy,内 target交给劈斩
额外指令函数里可做与形参无关的事墓地阴魂、捉迷藏
多次调用一函数处理多个目标捉迷藏、森林矿工
参数 + if/else按参数是否为空分流盗墓者、森林矿工
参数 + 距离过滤用距离筛选该不该打交给劈斩、林地陷阱

会传参、懂复用的英雄,才是真正灵活机变的英雄。
继续守护边际森林吧,勇敢的程序员!🌲🛡️🎯

CodeCombat 编程知识 · 边际森林:第七单元

CodeCombat教研团队