递归的底层原理

it2025-07-07  8

一、问题

C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。这里有一个简单的程序,可用于说明递归。程序的目的是把一个整数从二进制形式转换为可打印的字符形式。

例如,给出一个值4267,我们需要依次产生字符‘4’、‘2’、‘6’和‘7’。比如在printf函数中使用了%d格式码,它就会执行这类处理。

void binary_to_ascii(unsigned int value) { unsigned int quotient; quotient = value / 10; if (quotient != 0) binary_to_ascii(quotient); putchar(value % 10 + '0'); }

 下面是这个函数的工作流程 1、将参数值除以10。 2、如果 quotient的值为非零,调用 binary_to_ascii打印 quotient当前值的各位数字。 接着,打印步骤1中除法运算的余数。 注意:在第2个步骤中,我们需要打印的是 quotient当前值的各位数字。我们所面临的问题和最初的问题完全相同,只是变量 quotient的值变小了。我们用刚刚编写的函数(把整数转换为各个数字字符并打印出来)来解决这个问题。由于 quotient的值越来越小,所以递归最终会终止。

二、解析

程序中的函数是递归性质的,因为它包含了一个对自身的调用。乍一看,函数似乎永远不会终止。当函数调用时,它将调用自身,第2次调用还将调用自身,以此类推,似乎会永远调用下去。但是,事实上并不会出现这种情况。

这个程序的递归实现了某种类型的螺旋状while循环。 while循环在循环体每次执行时必须取得某种进展,逐步迫近循环终止条件。

递归函数也是如此,它在每次递归调用后必须越来越接近某种限制条件。当递归函数符合这个限制条件时,它便不再调用自身。 在程序中,递归函数的限制条件就是变量 quotient为零。在每次递归调用之前,我们都把quotient除以10,所以每递归调用一次,它的值就越来越接近零,当它最终变成零时,递归便终止。

好了,问题来了,第二次进入binary_to_ascii这个函数,那么变量quotient是怎么存储数据的?上一次的数据是被覆盖了吗?

三、追踪递归函数

但是,为了能理解递归的工作原理,你需要追踪递归调用的执行过程,所以让我们来进行这项工作。追踪一个递归函数执行过程的关键是理解函数中所声明的变量是如何存储的。当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量仍保留在堆栈上,但它们被新函数的变量所掩盖,因此是不能被访问的。当递归函数调用自身时,情况也是如此。每进行一次新的调用,都将创建一批变量,它们将掩盖递归函数前一次调用所创建的变量。当我们追踪一个递归函数的执行过程时,必须把分属不同次调用的变量区分开来,以避免混淆。 上个程序中的函数有两个变量:参数vaue和局部变量 quotient。

下面的一些图显示了堆栈的状态:当前可以访问的变量位于栈顶。所有其他调用的变量饰以灰色阴影,表示它们不能被当前正在执行的函数访问。假定我们以4267这个值调用递归函数。

当函数刚开始执行时,堆栈的内容如下图所示:

执行除法运算后,堆栈的内容如下:

 

接着,由if语句判断出quotient不为0,所以对该函数执行递归调用。当这个函数被第二次调用之初,堆栈的内容如下:

堆栈上创建了一批新的变量,隐藏了前面的那批变量。除非当前这次递归调用返回,否则它们是不能被访问的。再次执行除法运算之后,堆栈的内容如下:

quotient的值现在为42,仍然非零,所以需要继续执行递归调用,并再创建一批变量。在执行完这次调用的除法运算之后,堆栈的内容如下:

继续执行递归

不算递归调用语句本身,到目前为止所执行的语句只是除法运算以及对 quotient的值进行校验。

由于递归调用使这些语句重复执行,所以它的效果类似循环:当 quotient的值非零时,把它的值作为初始值重新开始循环。但是,递归调用将会保存一些信息(这点与循环不同),也就是保存在堆栈中的变量值。这些信息很快就会变得非常重要。

现在 quotient的值变成了零,递归函数便不再调用自身,而是开始打印输出。然后函数返回并开始销毁堆栈上的变量值。

每次调用 putchar得到变量 value的最后一个数字,方法是对 value进行模10取余运算,其结果是一个0到9之间的整数。把它与字符常量“0’相加,其结果便是对应于这个数字的ASCII字符,然后把这个字符打印出来。

 

最新回复(0)