函数
复习
- 高级语言:再加一层:从“控制机器”转向“描述问题”
- 变量、类型与表达式:名字如何代表数据,表达式如何变成运算
- 分支与循环:高级语言结构如何落到比较和跳转指令
TL;DR
- 函数把一段工作打包并命名,需要时按名字调用
- 参数是传给它的输入,返回值是它交回的结果
- 调用函数=跳进去执行,做完再跳回来
- “怎么跳回来、局部数据放哪”先当黑箱,下一章打开
正文
试试写这样一个程序:求两个数的最大值。上一章我们已经会了:
if x > y:
m = x
else:
m = y
可如果程序里有五处都要“求最大值”,难道把这四行抄五遍?
抄写的问题不只是累,更要命的是 改起来麻烦 :哪天逻辑要调整,五处都得改,漏一处就出 bug。这和前面“手工数地址”的困境如出一辙。
老办法还是那句: 起名字,打包。
把一段工作命名
我们把“求最大值”这件事打包成一个 函数 (function):
function max(a, b):
if a > b:
return a
else:
return b
这段代码给“求最大值”起了个名字 max,并规定它收两个输入 a、b,算完交回一个结果。用起来就像用一个新指令:
m = max(x, y)
这一行叫 函数调用 (call)。上面那段定义,则叫 函数定义 。
参数与返回值
函数和外界打交道,靠两样东西:
- 参数 (parameter):
a、b就是参数,是调用者传进来的 输入 - 返回值 (return value):
return后面那个值,是函数交还给调用者的 输出
一旦进到函数内部,a、b 就像两个普通的局部变量——它们只在函数里有意义。同一个函数被不同地方调用,各次传入的 a、b 互不干扰。
你也许会觉得,这不就是把代码换个写法吗?可它的意义很大: 函数让“一段工作”有了名字和边界 。调用者只需知道“max 能给我较大的那个数”,不必关心里面怎么比。又是一层抽象。
调用时机器在忙什么
高级语言里一句 m = max(x, y),落到机器上,大致是三步:
- 把参数
x、y送到函数能拿到的地方 - 跳到
max的代码去执行 - 算完之后,带着结果跳回原来那句话的后面,继续往下走
第 2 步不难,就是一次跳转。真正的谜题在第 3 步: 函数怎么知道该跳回哪里?
同一条函数可能被调用于很多个不同的地方,返回时得跳回“当初调用的那处”。这个“回来的地址”必须被记下来,而且——如果函数里又调用了别的函数呢?——得能一层层记下去。这里显然藏着一点门道。
还有,函数的参数和局部变量又存在哪里?总不能全塞进那几个固定寄存器里。
先留一个黑箱
这一章,我们先把“调用与返回”当成一个黑箱: 给它参数,它给回结果 ,中间怎么办到的,暂时不追究。
下一章,我们再打开这个黑箱,看看“记住回来的路”“安置局部数据”这两件事,是怎么用一个叫作 栈 的东西一次解决的。
思考题
假设
max函数被程序里三个地方调用。返回时,它怎么知道该回到哪一处?如果把“回来的地址”只记在一个固定位置,会发生什么问题?
小结
知识点
- 函数:给一段工作命名并打包
- 参数是输入,返回值是输出
- 函数调用=传参、跳入、执行、返回
- 函数让代码可复用、可读、便于修改
参考资料
- Wikipedia(zh):子程序:函数与过程
- Wikipedia(zh):参数:函数的参数与返回值
思考题答案(仅供参考)
函数必须在调用时把“回来的地址”记下来,返回时再跳回那里。如果只用一个固定位置记录返回地址,那么当 max 里又调用了别的函数、或者发生更深的调用时,后来的返回地址会把先前的覆盖掉,返回时就跳错了地方。这正是下一章要用“栈”解决的问题:把一串返回地址按顺序叠起来,后进的先出。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪