5

我正在自己练习 C 中的递归,我在网上找到了这个例子。但是有一点我不明白。

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[])
{
  singSongFor(4);
  return 0;
}

输出是这样的:

墙上有 4 瓶啤酒。4瓶啤酒。拿下一个,传来传去,墙上有 3 瓶啤酒。

墙上有 3 瓶啤酒。3瓶啤酒。拿下一个,传过来,墙上有2瓶啤酒。

墙上有 2 瓶啤酒。2瓶啤酒。拿下来,传过来,墙上有1瓶啤酒。

1 瓶啤酒在墙上。1瓶啤酒。拿下一个,传过来,墙上0瓶啤酒。

墙上根本没有啤酒瓶。

将一瓶放入回收箱,1 个空瓶放入垃圾箱。

将一瓶放入回收箱,2 个空瓶放入垃圾箱。

将一瓶放入回收箱,3 个空瓶放入垃圾箱。

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

我非常理解第一部分,直到我来到“墙上根本没有啤酒瓶。我不明白瓶子的可变数量是如何从 1 增加到 4 的。

4

7 回答 7

6

较小的啤酒瓶(及其相应的回收)处于内部功能。您的函数树如下所示:

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, 1 empty bottles in the bin.
|   |   Put a bottle in the recycling, 2 empty bottles in the bin.
|   Put a bottle in the recycling, 3 empty bottles in the bin.
Put a bottle in the recycling, 4 empty bottles in the bin.
于 2013-12-21T02:03:32.320 回答
4

一张简单的图说明:

在此处输入图像描述

于 2013-12-21T02:50:25.017 回答
4

逐步了解 this is 函数在调试器中的作用,您将确切了解此递归是如何工作的。我不是迂腐;我真的想不出比参考这种交互式方法更好的方法来说明这是做什么的。

于 2013-12-21T02:04:26.073 回答
3

请注意,最后一个printf使用numberOfBottles变量,并且永远不会修改。因此,在从打印oneFewer瓶子返回时,它将打印带有numberOfBottles. 请记住,每次调用函数都有一个不同的局部变量化身。

如果您缩进对函数的调用,可以更容易地看到:

4 bottles of beer on the wall...
  3 bottles of beer on the wall...
    2 bottles of beer on the wall...
      1 bottles of beer on the wall...
        There are simply no more bottles of beer on the wall.
      Put a bottle in the recycling, 1 empty bottles in the bin.
    Put a bottle in the recycling, 2 empty bottles in the bin.
  Put a bottle in the recycling, 3 empty bottles in the bin.
Put a bottle in the recycling, 4 empty bottles in the bin.

现在,从同一列开始的每一行都是从函数的同一次调用中写入的。你看瓶子的数量和回收率如何?那是因为两者都使用相同的变量:numberOfBottles.

于 2013-12-21T02:00:53.727 回答
2

它以这种方式工作的原因是每次对singSongFor()numberOfBottles 大于 1 的调用将依次递归调用singSongFor(),直到 numberOfBottles 为 0。此时printf("There are simply no more bottles of beer on the wall.\n\n")到达并且该函数将完成,传递给调用函数,该函数将具有传入的参数为 1,然后到达printf("Put a bottle in the recycling, %d empty bottles in the bin.\n", numberOfBottles);并完成,返回到singSongFor(2)... 依此类推,直到你回到原来的数字,在这种情况下为 4。

于 2013-12-21T02:07:28.193 回答
2

回收语句在递归调用返回后运行。

每个递归调用最终都会完成,程序将继续执行后面的回收语句,每个语句都有自己的局部变量值。

于 2013-12-21T02:02:27.690 回答
2

使用递归,您可以将递归调用之前的所有内容视为前向循环,将调用之后的所有内容视为后向循环。(尾递归 - 在函数末尾再次调用函数 - 通常由编译器优化为简单的前向循环。)

它的工作方式是将每个旧函数的参数推入堆栈,并在全部返回时弹出。请记住,堆栈是后进先出的。因此,由于您从 4 开始,它会推送 4,然后是 3,然后是 2,然后是 1。当函数返回时,堆栈开始展开,因此您再次以相反的顺序看到参数:1、2、3、4。

于 2013-12-21T02:03:17.987 回答