C允许函数调用他自己,这种调用的过程成为递归(recursion)。递归有时难以捉摸,有时却很方便实用。结束递归是使用递归的难点,因为如果递归代码中没有终止递归的条件测试部分,一个调用自己的函数就会无限递归(有点像循环)
演示递归
/*recur.c -- 递归演示*/
#include <stdio.h>
void up_and_down(int);
int main (void)
{
up_and_down(1);
return 0;
}
void up_and_down(int n)
{
printf("Level %d: n location %p\n", n, &n); // #1
if (n < 4){
up_and_down(n + 1);
}
printf("LEVEL %d: n location %p\n", n, &n); // #2
}
下面是Windos系统的输出结果:
首先,main()调用了带参数1的up_and_down()函数,执行结果是up_and_down()中的形参n的值是1,打印语句#1 Level1。然后由于n<4,up_and_down()(第1级)调用实际参数为n+1(2)的up_and_down()(第2级),于是第2级调用的n值是2,打印语句#1, Level2。以此类推到Level4.
当执行到第4级的时候,n的值是4,使用if测试条件为假。up_and_down()函数不再调用自己。第4级调用接着执行写一条语句,也就是#2,即打印LEVEL4。此时第4级调用结束,控制被传回他的主调函数(第3级),接着打印LEVEL3,以此类推到LEVEL1.
尾递归
最简单的递归形式是把递归放在函数的末尾,在return语句前,这种形式的递归被称为尾递归。相当于循环。
下面是一个分别用循环和递归计算阶乘的程序:
/*factor.c -- 分别用循环和递归计算阶乘*/
#include <stdio.h>
long fact(int n);
long rfact(int n);
int main (void)
{
int num;
printf("计算阶乘!\n");
printf("输入一个0-12是数(q退出):\n");
while (scanf("%d", &num) == 1){
if(num < 0 || num > 12){
printf("输入错误,范围0-12!\n");
}else{
printf("循环:%d!结果为%ld\n", num, fact(num));
printf("递归:%d!结果为%ld\n", num, rfact(num));
}
}
printf("结束,告辞!\n");
return 0;
}
long fact(int n){
long ans;
for(ans = 1; n > 1; n--){
ans *= n;
}
return ans;
}
long rfact(int n){
long ans;
if(n > 1){
ans = n * rfact(n - 1);
}else{
ans = 1;
}
return ans;
}
运行结果为:
计算阶乘!
输入一个0-12是数(q退出):
5
循环:5!结果为120
递归:5!结果为120
12
循环:12!结果为479001600
递归:12!结果为479001600
13
输入错误,范围0-12!
0
循环:0!结果为1
递归:0!结果为1
-2
输入错误,范围0-12!
q
使用循环的函数把初始值ans初始化为1,如何与n~12的数相乘,根据阶乘公式应当乘1,但没有影响。
使用递归的函数,该函数的关键是n! = n * (n-1)! 。可以这样做是因为(n-1)!是(n-1)~ 1的所有整数的乘积。因此,n乘以n-1就得到了n的阶乘。
既然递归和循环都可以用来计算,那么到底该用谁呢?
一般而言,选择循环比较好。
首先,使用递归每次都会创建一组变量,所以递归使用的内存更多,而且每次递归都会创建一个新的变量在栈内存中,因此递归受到内存的限制。其次每次还是调用都要花费一定的时间,使用递归执行的速度会比较慢。
递归和倒序计算
递归在处理倒序计算是比较方便。
编写一个函数,打印一个整数的二进制数。
/*binary.c -- 打印一个二进制数*/
#include <stdio.h>
void to_binary(unsigned long n);
int main (void)
{
unsigned long n;
printf("Enter an integer (q to quit):\n");
while(scanf("%lu", &n) == 1){
printf("Binary equivalent:");
to_binary(n);
putchar('\n');
printf("Enter an integer (q to quit):\n");
}
printf("Done.\n");
return 0;
}
void to_binary(unsigned long n)
{
int r;
r = n % 2;
if(n >= 2){
to_binary(n/2);
}
putchar(r == 0 ? '0' : '1');
return;
}
运行结果为:
设计一个二进制形式表示整数的算法。例如,用二进制数表示十进制5?在二进制中,奇数末尾一定是1,偶数末尾一定是0,所以通过(5%2)来确定末尾是1还是0。一般而言,对于数字n,其二进制的最后一位是n%2。因此第一位数字实际上是待输出二进制数的最后一位数。这一规律提示我们,在递归函数调用之前计算n%2,递归调用之后打印结果。这样n%2的值,正好在最后一位。
递归的优缺点
优点:递归为某些编程问题提供了最简单的解决方案。
缺点:一些递归算法会快速消耗内存空间。