自学C之递归理解

版权声明:本文为博主原创文章,转载时请注明出处,谢谢关注。 https://blog.csdn.net/mikayong/article/details/51691253

一、 理解概念

  C语言允许一个函数调用自身,这种过程被称为递归(Recursion)。程序使用递归处理特殊的问题,如阶乘、 Ackermann函数,反序等等。实际上,如果不考虑运行时内存的开消,任何使用赋值语句、if-else和while结构的函 数都可以用递归方式重写。

  “递归”可以跟据字面去理解,“递”就是一级一级递进,“递”可以想象成通过一段台阶走下地下室,“归”则是归来 的意思,就像通个了“递”这些阶级到了地下室之后,又延着所走的台阶返回原点。

你还可以将“递归”想象成“我肚里面有一个我”,如果我肚里有一个我,那么肚里的我肚里还有一个我,这样一路我 下去,我我我我,将会无穷无尽,因而递归需要一个终止的条件,就假设世界上只能同时存在5个我,这样,“我肚里面 有我”就一共只有五个肚,五个我,最后第五个我的肚里不能再有我了。

  “我肚里有我”,这就相当函数里面有本函数,函数处理事务,就相当于我吃饭, 当我吃饭时,我里面的我也要吃 饭,但所有的我入口都是最外面的一张嘴,于是,我吃饭,饭到了肚子里被肚子里的我吃了我下去的饭,肚子里的我的 肚子里的我再把他的饭吃了,这样的情况,只有把最里面的我的肚子喂饱了,外面的我才可以吃到饭。程序的函数也是 一样,它必须要等里面的函数处理完了问题,外面的函数才可以着手处理问题。就好比去地下室拿东西,你必须走完所 有阶梯才到达地下室,然后才可以返回。

  总之,我们记着,只要是递归,就必须要等里面的我(函数)完成了任务,才轮到外面一级的我(函数)做任务。

二、 递归的基本原理

   1. 每一级的函数调用都有自已的变量。

   2. 每一次函数调用都会有一次返回。

   3. 递归函数中,位于递归调用前的语句和各级被调函数具有相同的执行顺序。

   4. 递归函数中,位于递归调用后的语句和各级被调函数的顺序相反。

   5.虽然每一级都有自己的变量,但是函数代码并不会复制。函数代码是一系列的计算机指令,而函数调用就是从头执行这个指令集的一条命令。

   6.递归函数中必须包含可以终止递归的语句。

三、原理验证

#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);
    if(n < 3) 
        up_and_down(n+1);
    printf("Level %d: n location %p\n", n, &n);
}

编译后执行的结果:


Level 1: n location 0x7fffffffe30c 从level1、level2、level3中可以看出n有三个不同的地址,即是说经过三次递归调用,各有不同的n变量。

Level 2: n location 0x7fffffffe2ec 下面的3级level,可以看出3次递归调用返回了三次,验证了每次调用都有返回。

Level 3: n location 0x7fffffffe2cc 第一和第二个printf用来验证递归调用前的函数执行顺序,验证了它和递归调用有相同的执行顺序。

Level 3: n location 0x7fffffffe2cc 第三和第四个printf则说明了递归后的函数是以反序方式来执行的

Level 2: n location 0x7fffffffe2ec

Level 1: n location 0x7fffffffe30c


我们再用GDB跟踪程序:


先用gdb 加载程序,键入start运行后停在main入口处

Temporary breakpoint 2, main () at recur.c:6
6 up_and_down(1);

stepi跟踪程序进入调用函数

(gdb) stepi
0x0000000000400589 6 up_and_down(1);

用backtrace查看内存运行栈,看出函数已压入栈中

(gdb) bt
#0 up_and_down (n=0) at recur.c:11
#1 0x000000000040058e in main () at recur.c:6

往下执行到n=3时,栈中已压入三个函数在main函数之上,从中可以看出,每一次函数调用,就需要向栈中压入数据,因为每次调用都要压栈,所以每次调用的函数的变量都是独立的,又所以每一次调用都会有返回。

(gdb) bt
#0 up_and_down (n=3) at recur.c:13
#1 0x00000000004005d7 in up_and_down (n=2) at recur.c:15
#2 0x00000000004005d7 in up_and_down (n=1) at recur.c:15
#3 0x000000000040058e in main () at recur.c:6

因为栈的特性,后进入栈的函数获得先返回的机会,所以位于递归调用后的语句和各级被调函数的顺序相
反,当n>时函数开始返回,首先返回的是n=3时的函数。
跟上面比较, up_and_down(n=3)的函数已经返回

(gdb) bt
#0 up_and_down (n=2) at recur.c:17
#1 0x00000000004005d7 in up_and_down (n=1) at recur.c:15
#2 0x000000000040058e in main () at recur.c:6

往下执行直到main返回,程序结束。

(gdb) c
Continuing.
Level 2: n location 0x7fffffffe2ec
Level 1: n location 0x7fffffffe30c
[Inferior 1 (process 6893) exited normally]

用GDB调试可以看出,递归如果层数太多,超出了栈的容量就会造成程序死机,这就是递归的缺点;同时,函数调用每次都要压栈处理,递归层数过多就会影响程序的运行速度。

四、递归处理反序

返回值需要反序排列时,递归能有效地精简代码

例如:十进制转换成二进制,用除二取余,倒序排列
意思是:将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取将除得的余数,即换算为二进制数的结果。 例如把52换算成二进制数,计算结果如图:
除二取余
52除以2得到的余数依次为:0、0、1、0、1、1,倒序排列,所以52对应的二进制数就是110100。

用C语言可以这样编码:

#include <stdio.h>

void to_binary(int);

int main(void)
{
    int number;
    printf("Enter a number or 'q' to exit:\n");
    while (scanf("%d", &number) == 1)
    {
        printf("Binary equivalent:");
        to_binary(number);
        putchar('\n');
        printf("Enter a number or 'q' to exit:\n");
    }
    printf("Done!\n");

    return 0;

}

void to_binary(int n) {
    int r;
    r = n%2;
    if (n >= 2)
        to_binary(n/2);
    putchar('0' + r);
    return;
}

上面用递归的方法比用循环的方法要简单很多,你可以用循环的方法重编上面的函数去对比。

猜你喜欢

转载自blog.csdn.net/mikayong/article/details/51691253
今日推荐