第 9 章:编写递归函数

第七章通过不断取列表的尾部完成求和。递归也能处理数字问题,关键是明确什么时候停止,以及下一次调用为什么更接近停止条件。

本章用阶乘练习这两点。非负整数的阶乘定义为:零的阶乘是 1,正整数的阶乘是它乘以比它小一的数的阶乘。

完整程序:计算阶乘

寻观「标准库」之书。

「阶乘」乃化「整数」而「整数」也。
「阶乘」者会「数」而
    若「等」于「数」于「零」
    则「一」
    否则「乘」于「数」于(「阶乘」于(「减」于「数」于「一」))也。

「打印行」于(「整数表示」于(「阶乘」于「六」))。

输出为 720。先声明函数类型,再定义函数体,这样检查递归调用时就能知道 「阶乘」 接收和返回什么类型。

用更小的输入 3 跟踪调用,计算可以展开为:

阶乘 3 = 3 × 阶乘 2
阶乘 2 = 2 × 阶乘 1
阶乘 1 = 1 × 阶乘 0
阶乘 0 = 1

最内层得到 1 后,外层依次完成乘法,最终得到 6。注意每一层都保留着自己的参数值,下一层的参数减一不会修改上一层的参数。

在表达式内部定义递归

如果辅助函数只在一个地方使用,可以用局部递归。下面的表达式从 3 开始不断减一,直到零;可以把它放在 「整数表示」 的参数位置打印结果:

递归虑「递减到零」其化「整数」而「整数」者
    会「数」而
        若「等」于「数」于「零」
        则「零」
        否则「递减到零」于(「减」于「数」于「一」)
而
    「递减到零」于「三」

递归虑 引入局部递归名称, 后面写类型, 后面写函数定义,最后的 后面是使用这个函数的表达式。这个辅助名称不需要放到文件顶层。

类型检查不能替代停止条件分析

本章阶乘只适用于非负整数。输入负数时,每次减一都会离零更远,不能到达停止分支。即使类型检查通过,也不意味着任意整数输入都能正常结束。

写递归时先用小输入手算几步:零会怎样?一步之后是什么输入?是否有某些输入永远到不了停止分支?同时注意数值范围,固定宽度整数不能容纳任意大的阶乘。

练习

编写接收底数和非负指数的 「幂」。指数为零时返回 1,否则把指数减一并递归。测试 2⁰,应分别得到 1、8、9。这里递减的是指数,底数应保持不变。

上一章 · 下一章:把程序拆成模块 · 目录