上一单元你学会了用 def 把行为封装成函数,比如 commandSoldiers() 一次遍历所有士兵下令。但那样的函数有个局限:它内部"要处理谁"是写死的。本单元你将给函数装上一个"入口"——参数(parameter),让同一个函数每次可以处理不同的对象。这样你就能写一个 commandSoldier(soldier),把任意一名士兵"递"给它去指挥。
def commandSoldier(soldier): 的括号里声明形参、在调用 commandSoldier(friend) 时把实参传进去、理解参数在函数内部就像一个局部变量、并用这套机制写出两个各带参数的函数——commandSoldier 和 commandArcher,分别指挥士兵和弓箭手。
没有参数的函数只能做一件固定的事;有了参数,同一个函数就能被"喂"进不同的数据,做同一类事情。关卡默认代码的注释点明了这个思想:"这个函数有一个名为 soldier 的参数。参数就像变量一样。参数的值是在调用函数时确定的。"换句话说,commandSoldier 不再"内定"要指挥谁,而是等你调用时告诉它"这次指挥这一个"。
def commandSoldiers(): for s in hero.findFriends(): # 内部自己找对象 pass
def commandSoldier(soldier): # soldier 由调用者传进来 enemy = soldier.findNearestEnemy() # ...
有两个词要分清:定义时括号里写的那个名字叫形参(形式参数,只是个占位的名字,如 soldier);调用时括号里真正传进去的值叫实参(实际参数,如 friend)。调用发生的一瞬间,实参的值会被"装进"形参这个名字里,函数体就拿着它开始工作。
commandSoldier(friend) 时,把 friend 这封"信"投进信箱。soldier 指代的就是这次投进来的那位士兵。
上一单元函数的括号总是空的 ()def commandSoldier(soldier): 就声明了这个函数需要"一个东西",并在函数内部用 soldier 这个名字来称呼它。这个名字是你自己起的,起得贴切能让代码更好读。
关卡说"参数就像变量一样"——这话很关键。一旦进入函数体,形参 soldier 就和你平时写的普通变量没有区别:你可以 soldier.findNearestEnemy()、可以 soldier.pos、可以拿它去 command。它是一个局部变量——只在这个函数内部有效,出了函数就"不存在"了。
def commandSoldier(soldier): # 在函数里,soldier 就是个普通变量,可自由使用 enemy = soldier.findNearestEnemy() if enemy: hero.command(soldier, "attack", enemy)
soldier 只在 commandSoldier 这个函数内部有意义。你没法在函数外面用它,别的函数也看不见它。每次调用函数,都会新建一个属于这次调用的 soldier,用完即弃。这种"各管各的、互不干扰"的特性,正是函数好用、不易出错的原因之一。
参数传递的本质,就是在调用的那一刻把实参"赋值"给形参。关卡默认代码里有一句提示注释:"将这位朋友赋值给 commandSoldier 函数中的 soldier 变量"——对应的调用是 commandSoldier(friend)。执行这行时,等价于在函数内部悄悄做了 soldier = friend,然后才开始跑函数体。
friend,函数里的形参叫 soldier——两个名字不同完全没问题。重要的是"值"的传递,不是名字的匹配。函数只关心"你递给我一个单位对象",至于它在外面叫什么,函数并不在乎。
本关的部队里既有士兵又有弓箭手,两者战术不同,需要分别指挥。做法是遍历所有盟友,用 friend.type 判断它是哪种兵,再把它"递"给对应的函数:士兵送进 commandSoldier(friend),弓箭手送进 commandArcher(friend)。这就是"按类型分派"——同一个 friend,根据身份走向不同的处理函数。
for friend in hero.findFriends(): if friend.type == "soldier": commandSoldier(friend) # 把这名士兵递进去 elif friend.type == "archer": commandArcher(friend) # 把这名弓箭手递进去
friend 每轮都是不同的单位。if/elif 就像一个岔路口的分拣员:看一眼 friend.type,是士兵就走左边这条路进 commandSoldier,是弓箭手就走右边进 commandArcher。参数机制让"把当前这个单位交给某函数"变得非常自然。
为什么要拆成两个函数?因为士兵和弓箭手的最优打法不同。关卡规定得很清楚:"弓箭手应该只攻击距离小于 25 米的敌人。士兵应该攻击任何敌人。"士兵是近战肉盾,见敌就冲;弓箭手是脆皮远程,必须克制——只打射程内的敌人,否则原地待命,绝不主动追出去。
士兵勇往直前:findNearestEnemy() 找到任何敌人就 command(soldier, "attack", enemy),冲上去打。
弓箭手克制作战:只在 distanceTo(enemy) < 25 时攻击,否则 command(archer, "move", archer.pos) 原地待命,守住驯鹿。
command(archer, "move", archer.pos)——命令它"待在自己当前位置不动",用射程守株待兔,敌人进 25 米才放箭。
这一节聚焦关卡的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
食人魔在猎捕你的驯鹿,你要反过来"猎猎手"。核心策略是分层布防:让士兵作为前排主动出击、把食人魔顶在外围;让弓箭手作为后排贴着驯鹿、当敌人闯进射程时才补刀。关卡简介一句话概括——"在召唤士兵进攻的同时,让弓箭手在士兵背后"。攻守分层,才能既清敌又不丢猎物。
算法结构是"若干带参数的函数 + 一个分派循环"。先补全两个无参函数 pickUpCoin(找最近金币就 move 过去)和 summonTroops(钱够就 summon("soldier"))。再写两个带参数的指挥函数:commandSoldier(soldier) 里 soldier.findNearestEnemy() 找到敌人就攻击;commandArcher(archer) 里 archer.findNearestEnemy() 后加一层距离判断 archer.distanceTo(enemy) < 25,够近才攻击、否则 command(archer, "move", archer.pos) 原地待命。最后在 while True 里遍历 findFriends(),按 friend.type 把每个单位分派给对应函数。
# 关键片段:弓箭手——够近才射,否则守在原地 def commandArcher(archer): enemy = archer.findNearestEnemy() if enemy and archer.distanceTo(enemy) < 25: hero.command(archer, "attack", enemy) else: hero.command(archer, "move", archer.pos) # 待在原地
这一关把参数传递用得淋漓尽致:两个指挥函数各带一个形参,分派循环把不同兵种的单位当作实参传进去。妙在 commandSoldier 和 commandArcher 结构几乎一样(都是"找敌人→判断→下令"),只是判断条件不同——这说明参数化的函数让"同类行为、不同细节"的代码得以清晰地各自封装,主循环则只负责"谁该进哪个函数"。
else 分支里的 command(archer, "move", archer.pos),弓箭手就会按默认行为主动追敌、脱离驯鹿,导致驯鹿被伏击。"没敌人/敌人太远时,明确命令弓箭手原地不动"是本关成败的关键。
archer.distanceTo(enemy) 算的是弓箭手到敌人的距离,而非英雄到敌人。因为判断"敌人是否进入这名弓箭手的射程",必须站在弓箭手自己的位置上量。这也再次体现了参数的价值——函数内用 archer 这个形参,就能针对"当前这名弓箭手"精确计算。
commandSoldier(),缺少实参会报错;应写 commandSoldier(friend)。def commandSoldier(): 括号空着,函数里却用 soldier,会因变量未定义而出错。soldier,函数体里却写 friend,两者对不上;函数内应统一用形参名。else 分支,弓箭手默认追敌脱离驯鹿,导致猎物被伏击。distanceTo(enemy) > 25,结果只打远处、近处不打。"archer" 写错或大小写不符,弓箭手永远进不了对应分支。hero.distanceTo(enemy),量的是英雄到敌人,而非弓箭手到敌人。"attack" 第三参数应是敌人对象,"move" 才传坐标/位置。恭喜你给函数装上了"输入口"!这一单元你理解了参数的威力:在 def commandSoldier(soldier): 的括号里声明形参,让同一个函数能处理不同对象;分清了形参(占位名)与实参(真值),明白调用 commandSoldier(friend) 的一瞬间等价于把 friend 赋值给 soldier;也知道了参数在函数内部就是一个局部变量,可以像普通变量一样使用。最后你用参数化的 commandSoldier 和 commandArcher,配合按 type 分派的循环,让士兵冲锋、弓箭手守猎物,打赢了"猎手和猎物"。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 声明形参 | def 名字(形参): | 猎手和猎物 |
| 形参 vs 实参 | 占位名 vs 传入的真值 | 猎手和猎物 |
| 参数传递机制 | 调用瞬间 形参 = 实参 | 猎手和猎物 |
| 参数是局部变量 | 只在函数内有效,可自由使用 | 猎手和猎物 |
| 同函数处理不同对象 | 每次调用喂进不同实参 | 猎手和猎物 |
| 按 type 分派 | if type=="soldier" 走不同函数 | 猎手和猎物 |
| 士兵战术 | 见敌就攻击 | 猎手和猎物 |
| 弓箭手战术 | 近才射,否则原地待命 | 猎手和猎物 |
懂得给函数递上不同的"输入"、让一个函数灵活服务千军万马的英雄,才是云峰之上真正老练的程序员。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️🦌🏹
扣哒世界 编程知识 · 计算机科学 5(安息云峰):第二单元
扣哒世界教研团队