第 9 章:编写递归函数
第七章通过不断取列表的尾部完成求和。递归也能处理数字问题,关键是明确什么时候停止,以及下一次调用为什么更接近停止条件。
本章用阶乘练习这两点。非负整数的阶乘定义为:零的阶乘是 1,正整数的阶乘是它乘以比它小一的数的阶乘。
完整程序:计算阶乘
寻观「标准库」之书。
「阶乘」乃化「整数」而「整数」也。
「阶乘」者会「数」而
若「等」于「数」于「零」
则「一」
否则「乘」于「数」于(「阶乘」于(「减」于「数」于「一」))也。
「打印行」于(「整数表示」于(「阶乘」于「六」))。
输出为 720。先声明函数类型,再定义函数体,这样检查递归调用时就能知道 「阶乘」 接收和返回什么类型。
用更小的输入 3 跟踪调用,计算可以展开为:
阶乘 3 = 3 × 阶乘 2
阶乘 2 = 2 × 阶乘 1
阶乘 1 = 1 × 阶乘 0
阶乘 0 = 1
最内层得到 1 后,外层依次完成乘法,最终得到 6。注意每一层都保留着自己的参数值,下一层的参数减一不会修改上一层的参数。
在表达式内部定义递归
如果辅助函数只在一个地方使用,可以用局部递归。下面的表达式从 3 开始不断减一,直到零;可以把它放在 「整数表示」 的参数位置打印结果:
递归虑「递减到零」其化「整数」而「整数」者
会「数」而
若「等」于「数」于「零」
则「零」
否则「递减到零」于(「减」于「数」于「一」)
而
「递减到零」于「三」
递归虑 引入局部递归名称,其 后面写类型,者 后面写函数定义,最后的 而 后面是使用这个函数的表达式。这个辅助名称不需要放到文件顶层。
类型检查不能替代停止条件分析
本章阶乘只适用于非负整数。输入负数时,每次减一都会离零更远,不能到达停止分支。即使类型检查通过,也不意味着任意整数输入都能正常结束。
写递归时先用小输入手算几步:零会怎样?一步之后是什么输入?是否有某些输入永远到不了停止分支?同时注意数值范围,固定宽度整数不能容纳任意大的阶乘。
练习
编写接收底数和非负指数的 「幂」。指数为零时返回 1,否则把指数减一并递归。测试 2⁰、2³ 和 3²,应分别得到 1、8、9。这里递减的是指数,底数应保持不变。
上一章 · 下一章:把程序拆成模块 · 目录