一、基本概念:
c语言通过运行时堆栈来支持递归的实现的。递归函数就是直接或者间接调用自身的函数。
这里有一个简单的程序,可用来说明递归。程序的目的是将一个整数从二进制形式转化为可打印的字符形式,例如给出一个值4267,我们需要一次产生‘4’,‘2’,‘6和‘7’。如果在printf函数中使用了%d格式码,它就会执行这类处理。
这里的算法思路是,把这个值反复除以10,并打印各个余数。例如,4267除10的余数是7,但是我们不能直接打印这个余数。我们需要打印的是机器字符集中表示数字‘7’的值。在ASCII码中,字符‘7’的值是55,所以我们需要在余数上加上48来获取正确的字符。但是,使用字符常量而不是整型常量可以提高程序的可移植性,
‘0’+0=‘0’
‘0’+1=‘1’
‘0’+2=‘2’
从这些关系中,我们很容易看出在余数上加上‘0’就可以产生对应字符的代码。接着就打印出余数。下一步是取得商,4267/10=426.然后用这个值重复上述这个步骤。
这种处理方法存在的唯一问题是它产生的数字次序正好相反,它们是逆序打印的,可以使用递归来修正这个问题。
#include<stdio.h>
void binary_to_ascii(unsigned int value)
{
unsigned int quotient;
quotient = value / 10;
if(quotient != 0)
binary_to_ascii(quotient);
putchar(value%10+'0');
}
这个函数就是递归性质的,因为它包含了一个对自身的调用。乍一看,函数似乎永远不会终止。但函数调用时,它将调用自身,一直调用自身,似乎会永远调用下去实际上并不会出现这种情况。
这个程序的递归实现了某种类型的螺旋状while循环。while循环在循环体每次执行时必须取得某种进展,逐步迫近终止条件。递归函数也是如此,它在每次递归调用后都必须越来越迫近某种限制条件。当递归函数符合这个条件时,它便不再调用自身。
在上述程序中,递归函数的限制条件是quotient为零,在每次递归调用之前,我们都把quotient除以10,所以每递归一次,它的值就越来越接近零。当它最终变成0时,递归便终止。
这段代码的工作流程是什么样子呢?
1、将参数值除以10
2、如果quotient的值为非零,调用binary_to_ascii打印quotient当前值的各位数字,
3、接着打印步骤1中除法运算的余数
注意:在第二个步骤中我们打印的是quotient当前值的各位数字。我们所面临的问题和最初的问题完全相同,只是变量quotient的值变小了。我们用刚刚编好的函数来解决这个问题,由于qutient的值越来越小,所以递归最终会终止。
为了理解递归的工作原理,我们需要追踪递归的执行过程,而追踪的关键就是变量是如何存储的。当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量仍保留在堆栈上,但它们被新函数的变量所掩盖,因此是不能被访问的。调用自身时也是相同的,每一次调用产生的新变量掩盖前一次创建的变量。
上面程序中一共有两个变量,一个是参数value一个是局部变量quotient过程如图所示,当前可以访问的变量位于栈顶,其它调用的变量用灰色阴影,表示它们不能被当前函数访问。
假定我们就以4267这个数字作为参数,刚开始执行时,堆栈的内容如下:
执行除运算后,堆栈变为:
经过if语句判断quotient的值不为0,开始递归调用,第二次调用开始,堆栈如图所示:
堆栈上创建了一批新的变量,隐藏了前面的那批变量,除非当前递归调用直接返回否则阴影部分变量就不能被访问,再次执行除法之后,堆栈内容如下:
if语句进行判断发现quotient的值是42仍然不是0,继续递归调用,并创建变量,执行完除法之后,堆栈内容如下:
if语句继续判断,发现quotient值为4,仍然不为0,继续执行递归调用,执行除法运算之后,堆栈内容如下:
现在quotient的值为0,if条件判断之后不再递归调用,开始打印输出,然后函数返回并开始销毁堆栈上的变量值。
每次调用putchar得到变量value的最后一个数字,方法是对value进行模10取余运算,其结果是一个0-9之间的整数,把它与‘0’相加,其结果便是对应这个数字的ASCII字符,然后把这个字符打印出来。
然后函数返回,变量从堆栈中销毁,接着递归调用的前一次继续执行,它所用的是自己的变量,它们位于堆栈的顶部,它的value值是42,所以使用putchar后打印出来的数字是2
接着递归函数继续返回,此时位于堆栈顶部的是递归函数前一次调用的变量,递归调用从这个位置开始继续执行,这次打印的数字是6,在这次调用返回之前,堆栈的内容如下:
回到最初的调用,这次调用打印出数字7,也就是它的value值除10的余数
堆栈内容如下:
到这里这个递归函数彻底返回到其他函数调用它的地点,这时候把打印出来的字符一个接一个拼在一起就能看到正确的结果:
4267