Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

函数

复习

  • 高级语言:再加一层:从“控制机器”转向“描述问题”
  • 变量、类型与表达式:名字如何代表数据,表达式如何变成运算
  • 分支与循环:高级语言结构如何落到比较和跳转指令

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,并规定它收两个输入 ab,算完交回一个结果。用起来就像用一个新指令:

m = max(x, y)

  这一行叫 函数调用 (call)。上面那段定义,则叫 函数定义

参数与返回值

  函数和外界打交道,靠两样东西:

  • 参数 (parameter):ab 就是参数,是调用者传进来的 输入
  • 返回值 (return value):return 后面那个值,是函数交还给调用者的 输出

  一旦进到函数内部,ab 就像两个普通的局部变量——它们只在函数里有意义。同一个函数被不同地方调用,各次传入的 ab 互不干扰。

  你也许会觉得,这不就是把代码换个写法吗?可它的意义很大: 函数让“一段工作”有了名字和边界 。调用者只需知道“max 能给我较大的那个数”,不必关心里面怎么比。又是一层抽象。

调用时机器在忙什么

  高级语言里一句 m = max(x, y),落到机器上,大致是三步:

  1. 把参数 xy 送到函数能拿到的地方
  2. 跳到 max 的代码去执行
  3. 算完之后,带着结果跳回原来那句话的后面,继续往下走

  第 2 步不难,就是一次跳转。真正的谜题在第 3 步: 函数怎么知道该跳回哪里?

  同一条函数可能被调用于很多个不同的地方,返回时得跳回“当初调用的那处”。这个“回来的地址”必须被记下来,而且——如果函数里又调用了别的函数呢?——得能一层层记下去。这里显然藏着一点门道。

  还有,函数的参数和局部变量又存在哪里?总不能全塞进那几个固定寄存器里。

先留一个黑箱

  这一章,我们先把“调用与返回”当成一个黑箱: 给它参数,它给回结果 ,中间怎么办到的,暂时不追究。

  下一章,我们再打开这个黑箱,看看“记住回来的路”“安置局部数据”这两件事,是怎么用一个叫作 的东西一次解决的。

思考题

  假设 max 函数被程序里三个地方调用。返回时,它怎么知道该回到哪一处?如果把“回来的地址”只记在一个固定位置,会发生什么问题?

小结

知识点

  • 函数:给一段工作命名并打包
  • 参数是输入,返回值是输出
  • 函数调用=传参、跳入、执行、返回
  • 函数让代码可复用、可读、便于修改

参考资料

  1. Wikipedia(zh):子程序:函数与过程
  2. Wikipedia(zh):参数:函数的参数与返回值

思考题答案(仅供参考)

  函数必须在调用时把“回来的地址”记下来,返回时再跳回那里。如果只用一个固定位置记录返回地址,那么当 max 里又调用了别的函数、或者发生更深的调用时,后来的返回地址会把先前的覆盖掉,返回时就跳错了地方。这正是下一章要用“栈”解决的问题:把一串返回地址按顺序叠起来,后进的先出。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪