递归如何在C中起作用

时间:2011-04-12 06:53:35

标签: c recursion

我是C的新手,我正在阅读有关递归的内容,但我完全感到困惑。

我感到困惑的主要部分是当达到退出条件时,事情是如何放松的。我想知道在递归值中如何从堆栈推送和弹出。

还有谁能请给我一个关于递归的图解视图?

...谢谢

5 个答案:

答案 0 :(得分:19)

让我们假设一个函数:

int MyFunc(int counter) {
    // check this functions counter value from the stack (most recent push)

    // if counter is 0, we've reached the terminating condition, return it
    if(counter == 0) {
        return counter;
    }
    else {
        // terminating condition not reached, push (counter-1) onto stack and recurse
        int valueToPrint = MyFunc(counter - 1);

        // print out the value returned by the recursive call 
        printf("%d", valueToPrint);

        // return the value that was supplied to use 
        // (usually done via a register I think)
        return counter;
    }
}

int main() {
    // Push 9 onto the stack, we don't care about the return value...
    MyFunc(9);
}

输出为:0123456789

第一次通过MyFunc,count是9.它没有终止检查(它不是0),所以调用递归调用,使用(counter -1),8。

这会重复,每次都会减少压入堆栈的值,直到counter == 0.此时,终止子句将触发,函数只返回counter(0)的值,通常在寄存器中。

下一次调用堆栈,使用返回的值print(0),然后返回调用它时提供给它的值(1)。这重复:

下一次调用堆栈,使用返回值print(1),然后返回调用它时提供给它的值(2)。等等,直到你到达顶部..

因此,如果使用3调用MyFunc,则会得到相当于(忽略堆栈中的返回地址等):

Call MyFunc(3) Stack: [3]
Call MyFunc(2) Stack: [2,3]
Call MyFunc(1) Stack: [1,2,3]
Call MyFunc(0) Stack: [0,1,2,3]
Termination fires (top of stack == 0), return top of stack(0).
// Flow returns to:
MyFunc(1) Stack: [1,2,3]
Print returned value (0)
return current top of stack (1)

// Flow returns to:
MyFunc(2) Stack: [2,3]
Print returned value (1)
return current top of stack (2)

// Flow returns to:
MyFunc(3) Stack: [3]
Print returned value (2)
return current top of stack (3)

// and you're done...

答案 1 :(得分:6)

  

达到退出条件后如何放松?

首先,关于递归的几句话:一个divide and conquer method用于复杂的任务,可以逐步分解并简化为初始任务的简单实例,直到表单基本情况允许直接计算。这是一个与mathematical induction密切相关的概念。

更具体地说,递归函数直接或间接地调用自身。在直接递归函数中,foo()再次调用自身。在间接递归中,函数foo()调用函数moo(),函数foo()又调用函数int fact(int n){ if(n == 0){ return 1; } return (n * fact(n-1)); } ,直到达到基本情况,然后,最终结果在精确中累积初始递归函数调用的逆序。

实施例

  

因子 n ,表示为 n!,是 1 n 的正整数的乘积。阶乘可以正式定义为:
阶乘(0)= 1 ,(基本情况
阶乘(n)= n * factorial(n-1) n> 0 的。 (递归调用

递归显示在此定义中,因为我们根据 factorial(n-1)定义 factrorial(n)

每个递归函数都应该有终止条件来结束递归。在此示例中,当 n = 0 时,递归停止。以 C 表示的上述函数是:

@Bean
MessageEndpointAdapter messageEndpointAdapter() {
    MessageEndpointAdapter adapter = new MessageEndpointAdapter();
    return adapter;
}

此示例是直接递归的示例。

如何实现?在软件层面,其实现与实现其他功能(程序)没有区别。一旦你理解了每个过程调用实例与其他实例不同,递归函数调用自身这一事实并没有太大的区别。

每个活动程序都维护一个 activation record ,它存储在堆栈中。激活记录由参数返回地址(调用者)局部变量组成。

激活记录在调用过程时存在,并在过程终止后消失,并将结果返回给调用者。因此,对于未终止的每个过程,存储包含该过程状态的激活记录。激活记录的数量,以及运行程序所需的堆栈空间量,取决于递归的深度。

  

也有人可以给我一个递归的图解视图吗?

下图显示 factorial(3)的激活记录

enter image description here

从图中可以看出,每次调用阶乘都会创建一个激活记录,直到达到基本情况为止,从那里开始我们以产品的形式累积结果。

答案 2 :(得分:5)

在C递归中就像普通函数调用一样。

  1. 调用函数时,参数,返回地址和帧指针(我忘记了顺序)被压入堆栈。
  2. 在被调用函数中,首先将局部变量的空间“推”到堆栈上。
  3. 如果函数返回某些内容,则将其放入某个寄存器(取决于架构,AFAIK)
  4. 撤消第2步。
  5. 撤消第1步。
  6. 因此,使用递归步骤1和2执行几次,然后可能执行3次(可能只执行一次),最后执行4次和5次(多次为1次和2次)。

答案 3 :(得分:3)

另一个答案是,一般情况下你不知道。 C作为一种语言没有任何堆栈。您的编译器使用称为堆栈的内存位置来存储控制流信息,例如堆栈帧,返回地址和寄存器,但C中没有任何内容禁止编译器将该信息存储在其他位置。对于实际方面,先前的答案是正确的。这就是C编译器今天的运作方式。

答案 4 :(得分:1)

这个问题得到了广泛的回答。请允许我使用更加教学的方法来补充一个额外的答案。

您可以将函数递归视为一堆 bubbles ,具有两个不同的阶段:推动阶段和爆发阶段。

A)推动阶段(或“推动堆叠”,如OP所称)

0)起始 Bubble#0 是MAIN功能。它被这些信息搞砸了:

  • 局部变量。
  • 调用下一个Bubble#1(第一次调用递归 功能,MYFUNC)。

1) Bubble#1 在此信息中被炸毁:

  • 上一个Bubble#0的参数。
  • 必要时使用局部变量。
  • 返回地址。
  • 使用返回值终止检查(例如:if(counter == 0){return 1})。
  • 致电下一个泡泡#2。

请记住,这个气泡和其他气泡一样,是递归函数MYFUNC。

2)冒泡#2 与Bubble#1相同的信息被炸毁,从后者获得必要的输入(参数)。在此之后,您可以堆叠任意数量的气泡,以便根据气泡#1中列出的项目对信息进行充气。

i)所以你可以得到任意数量的气泡:泡泡#3,泡泡#4 ......,泡泡#i 。最后一个泡泡在终止检查中有一个NAIL。请注意!

B)爆发阶段(或“弹出堆栈”,OP调用它)

当你达到正向终止检查并且包含指甲的最后一个气泡爆裂时,就会发生这个阶段。

让我们说这个阶段发生在Bubble#3中。达到正向终止检查,泡泡#3爆裂。然后解放了这个泡沫中的NAIL。这个钉子落在泡泡#2下面并且爆裂了。在这种情况发生之后,指甲跟随其下降,直到它破裂泡泡#1。泡泡#0也是如此。重要的是要注意指甲跟随泡沫中的返回地址,此时它正在爆裂:地址告诉指甲落下时指示方向。

在此过程结束时,获得答案并将其传递给MAIN函数(或Bubble#0,当然不会破裂)。

C)图形化(如OP要求)

这是图解说明。它从底部发展,泡泡#0到顶部,泡泡#3。

/*Bubble #3 (MYFUNC recursive function): Parameters from Bubble #2,
local variables, returning address, terminating check (NAIL),
call (not used here, as terminating check is positive).*/

推动上面的泡沫↑--------------------------------- -------------------- 指甲落到泡泡#2

/*Bubble #2 (MYFUNC recursive function): Parameters from Bubble #1,
local variables, returning address, terminating check (not used),
call to Bubble #3.*/

推动上面的泡沫↑--------------------------------- -------------------- 指甲落到泡泡#1

/*Bubble #1 (MYFUNC recursive function): Parameters from Bubble #0,
local variables, returning address, terminating check (not used),
call to Bubble #2.*/

推动上面的泡沫↑--------------------------------- -------------------- 指甲落到泡泡#0

/*Bubble #0 (MAIN function): local variables, the first call to Bubble #1.*/

希望这种方法有助于某人。如果需要澄清,请告诉我。