对C中的递归感到困惑

时间:2014-12-20 05:27:07

标签: c recursion

我有以下代码:

#include <stdio.h>

void SingSongFor(int numberOfBottles){

    if (numberOfBottles == 0){
        printf("There are simply no more bottles of beer on the wall.\n\n");
    } else {

        printf("%d bottles of beer on the wall. %d bottles of beer.\n", numberOfBottles, numberOfBottles);

        int oneFewer = numberOfBottles - 1;
        printf("Take one down, pass it around, %d bottles of beer on the wall.\n\n", oneFewer);

        SingSongFor(oneFewer); // This function calls itself!

        // Print a message just before the function ends
        printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);
    }
}

int main(int argc, const char * argv[]) {

    // We could sing 99 verses, but 4 is easier to think about
    SingSongFor(4);

    return 0;
}

根据我的理解,程序必须在打印后终止:

  

墙上不再有啤酒瓶了。

但是怎么恢复打印:

  

将一个瓶子放入回收处,在垃圾箱中放入一个空瓶子。

     

在回收箱中放入一个瓶子,在垃圾箱中放入2个空瓶子。

     

将一个瓶子放入回收箱中,将三个空瓶放入垃圾箱。

     

将一个瓶子放入回收箱中,将4个空瓶放入垃圾箱。

if函数已经打印出一条消息,但是不是终止它也会转到else。这怎么可能?在&#34; numberOfBottles&#34;中,如何从1增加到4?

更新:这是我对代码的理解。如果我错了,请纠正我。

8 个答案:

答案 0 :(得分:21)

3瓶:

SingSong(3):
 PRINT 2 LINES
 SingSong(2):
     PRINT 2 LINES
     SingSong(1):
          PRINT 2 LINES
          SingSong(0):
               PRINT 1 LINES
          PRINT RECYCLE LINE
     PRINT RECYCLE LINE
 PRINT RECYCLE LINE

在最后的内部递归调用发生后,它会通过每个方法调用退出并调用回收消息。

答案 1 :(得分:21)

要了解您的程序为何如此行事,了解函数调用的工作原理是必要的。它的递归这一事实可能使编译器能够进行一些优化以提高程序的效率,但从概念上来说,递归并不能真正改变正在发生的事情。

首先,让我们使用非递归函数调用来检查与程序基本相同的替代程序。

void SingSongFor4(){

        printf("4 bottles of beer on the wall. 4 bottles of beer.\n");

        printf("Take one down, pass it around, 3 bottles of beer on the wall.\n\n");

        SingSongFor3();

        // Print a message just before the function ends
        printf("Put a bottle in the recycling, 4 empty bottles in the bin.\n");
    }
}

void SingSongFor3(){

        printf("3 bottles of beer on the wall. 3 bottles of beer.\n");

        printf("Take one down, pass it around, 2 bottles of beer on the wall.\n\n");

        SingSongFor2();

        // Print a message just before the function ends
        printf("Put a bottle in the recycling, 3 empty bottles in the bin.\n");
    }
}

void SingSongFor2(){

        printf("2 bottles of beer on the wall. 2 bottles of beer.\n");

        printf("Take one down, pass it around, 1 bottles of beer on the wall.\n\n");

        SingSongFor1();

        // Print a message just before the function ends
        printf("Put a bottle in the recycling, 2 empty bottles in the bin.\n");
    }
}

void SingSongFor1(){

        printf("1 bottles of beer on the wall. 1 bottles of beer.\n");

        printf("Take one down, pass it around, 0 bottles of beer on the wall.\n\n");


        printf("Put a bottle in the recycling, 1 empty bottles in the bin.\n");
    }
}

int main(int argc, const char * argv[]) {

    // We could sing 99 verses, but 4 is easier to think about
    SingSongFor4();

    return 0;
}

我希望很明显,每个函数都会打印几行,调用下一个函数,然后打印另一行。每个被调用的函数依次执行此操作,例如,打印SingSongFor4()的2行,然后调用SingSongFor3。这将打印其2行,然后调用SingSongFor2(),打印其行等等。 SingSongFor1()不会调用任何其他函数,因此它会打印所有三行,然后返回到SingSongFor2(),然后完成,依此类推。总之,当您按照函数调用&#34;向下&#34;时,您将获得X bottles on the wall/take one down的8行,然后是&#34; 4行&#34;将一个瓶子放入垃圾箱&#34;当你回来&#34; up&#34;相反的方向。

你的功能没有任何不同,只是它已被参数化并添加了一点逻辑以确定它应该像SingSongFor1()那样行动以及什么时候应该像其他行为一样3.我说它没有什么不同,只是在你的情况下你有一个程序文本的副本,每个程序的调用共享,而不是4个单独的(几乎相同的)文本副本。共享文本副本的原因是每个函数调用的本地上下文 - 参数,变量以及有关程序所在位置和程序执行状态的一些内务处理信息。

通常,此上下文信息包含在称为堆栈的特殊数据结构中。它被称为堆栈,因为您将一个堆叠在另一个上面,然后从&#34;顶部一次一个地移除它们。&#34;每个堆栈帧包含一个函数调用的上下文:参数 - 在您的情况下numberOfBottles;局部变量 - oneFewer;以及有关函数结束或返回时应执行哪个语句的信息。当调用函数时,对应于该调用的帧被压入堆栈并执行函数的文本。完成后,弹出框架,并在调用函数停止的位置恢复执行(为此目的,它存储在弹出的堆栈框架中)。它恢复使用新的&#34;顶部&#34;它的上下文的堆栈框架。

重要的是,你的递归函数的工作方式与任何其他函数完全相同 - 每次调用它都会得到一个新的自己的上下文,即使函数的文本是相同。它执行完成然后返回到上一个上下文 - 虽然具有不同的上下文,但它可能是相同的函数。

答案 2 :(得分:6)

递归函数调用堆叠。所以它是这样的:

SingSongFor(4)
  |
  v
SingSongFor(3)
  |
  v
SingSongFor(2)
  |
  v
SingSongFor(1)
  |
  v
SingSongFor(0)

最后一次打印打印出没有更多的瓶子然后返回,这就是 magic 发生的情况:它返回之前的调用,然后打印有关回收站的消息,并返回上一个调用,再次打印其消息,依此类推。

答案 3 :(得分:4)

行后

 SingSongFor(oneFewer); // This function calls itself

你有一个印刷品,即

 printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);

这就是程序的作用。使用存储在堆栈中的numberOfBottles值。

答案 4 :(得分:3)

一切都很正确。当您到达最后的消息There are simply no more bottles of beer on the wall.时,您的程序将返回到它被调用的位置(它在函数SingSongFor中使用参数1调用)。然后打印消息Put a bottle in the recycling, 1 empty bottles in the bin.并使用参数SingSongFor返回函数2中的上一个调用。像这样最多4个。

答案 5 :(得分:3)

之所以感到沮丧,是因为在打印当前功能中的瓶数之前,您正在调用下一个功能。因此,当它全部赶上时,最后一个函数首先打印出来。

目前:

    SingSongFor(oneFewer); // This function calls itself!
    // Print a message just before the function ends
    printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);

你想要什么:

    // Print a message just before the function ends
    printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);
    SingSongFor(oneFewer); // This function calls itself!

这样,你就可以得到你期望的输出,然后下一次降压就会发生。

答案 6 :(得分:2)

如果我理解你只想在递归完成后在回收站中打印一次总数,那么最简单的解决方案是使用wrapper(或助手)函数来调用SingSonfFor。通过使用包装器,您将原始numberOfBottles保留为循环计数,而每个递归位将减少一个。例如:

#include <stdio.h>
#include <stdlib.h>

void SingSongFor (int numberOfBottles){

    if (!numberOfBottles)
        return;

    int oneFewer = numberOfBottles - 1;

    printf (" %d bottles of beer on the wall. %d bottles of beer.\n", 
            numberOfBottles, numberOfBottles);

    printf (" Take one down, pass it around, %d bottles of beer on the wall.\n\n", 
            oneFewer);

    if ((oneFewer) == 0)
        printf (" There are simply no more bottles of beer on the wall.\n\n");
    else 
        SingSongFor (oneFewer); // This function calls itself!
}

void ss_helper (int numberOfBottles)
{
    SingSongFor (numberOfBottles);

    /* Print a message just before the function ends */
    printf(" Put a bottle in the recycling, %d empty bottles in the bin.\n",
        numberOfBottles);

    if (numberOfBottles >= 6)
        printf ("\n Now go sober up you lush...\n\n");
}

int main(int argc, const char *argv[]) {

    // We could sing 99 verses, but 4 is easier to think about
    int coldbeers = (argc > 1) ? atoi (argv[1]) : 4;
    // SingSongFor (coldbeers);
    ss_helper (coldbeers);

    return 0;
}

<强>输出:

$ ./bin/beersong
 4 bottles of beer on the wall. 4 bottles of beer.
 Take one down, pass it around, 3 bottles of beer on the wall.

 3 bottles of beer on the wall. 3 bottles of beer.
 Take one down, pass it around, 2 bottles of beer on the wall.

 2 bottles of beer on the wall. 2 bottles of beer.
 Take one down, pass it around, 1 bottles of beer on the wall.

 1 bottles of beer on the wall. 1 bottles of beer.
 Take one down, pass it around, 0 bottles of beer on the wall.

 There are simply no more bottles of beer on the wall.

 Put a bottle in the recycling, 4 empty bottles in the bin.

答案 7 :(得分:1)

在您的代码中:

    SingSongFor(oneFewer); // This function calls itself!

    // Print a message just before the function ends
    printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);

SingSongFor函数的调用在完成之前不会返回(并且它调用的所有子函数也已完成)。

printf强制等待,直到所有较低SingSongFor次调用已完成,然后才能运行。

换句话说,调用函数不会将其排队等待将来执行,它会立即运行该函数 ,并且暂停执行当前函数,直到被叫功能已经完成。

此过程是函数调用创建执行状态的 堆栈 的原因,每个堆栈条目保存在该级别执行时将需要的本地值(在这种情况,只是numberOfBottles变量,以及等待运行的下一条指令的内存地址。

这种堆栈条目的推送和弹出会使递归(短暂地)消耗大量内存,而在更坏的情况下,当堆栈填满太多内存时,这可能导致...... 堆栈溢出