C 理解啤酒瓶示例中的递归

C 理解啤酒瓶示例中的递归,c,recursion,procedural,C,Recursion,Procedural,我自己在用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

我自己在用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瓶啤酒。 拿下一瓶,传来传去,墙上挂着两瓶啤酒

墙上有两瓶啤酒。两瓶啤酒。 拿下一瓶,传来传去,墙上挂着一瓶啤酒

墙上有一瓶啤酒。一瓶啤酒。 拿下一瓶,传来传去,墙上挂着0瓶啤酒

墙上再也没有啤酒了

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

把一个瓶子放进回收站,两个空瓶子放进垃圾箱

把一个瓶子放进回收站,3个空瓶子放进垃圾箱

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


我对第一部分理解得很好,直到我明白为止“墙上再也没有啤酒了。后来我不明白瓶子的可变数量是如何从1增加到4的。

请注意,最后一个
printf
使用
numberoflabels
变量,并且从未修改过。因此,当打印完
oneless
瓶子返回时,它将使用
numberoflabels
打印回收文本。请记住,每个函数调用都有一个不同的局部变量

如果缩进对函数的调用,则更容易看到:

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.

现在,从同一列开始的每一行都是从同一个函数调用中写入的。你看到瓶子和回收硬币的数量了吗?这是因为两者都使用相同的变量:
numberofflags

循环语句在递归调用返回后运行


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

使用递归,可以将递归调用之前的所有内容视为前向循环,调用之后的所有内容视为后向循环。(尾部递归——在函数结束时再次调用函数——通常由编译器优化为一个简单的正向循环。)


其工作方式是将每个旧函数的参数推送到堆栈上,并在所有函数返回时弹出。请记住,堆栈是后进先出的。因为你从4开始,它推动4,然后3,然后2,然后1。当函数返回时,堆栈开始展开,因此您可以按相反的顺序再次看到参数:1,2,3,4。

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

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.

逐步了解这个函数在调试器中所做的工作,您将确切地看到这个递归是如何工作的。我不是学究;我真的想不出比引用这种交互式方法更好的方法来说明这一点。

它之所以能这样工作,是因为当NumberOfBaggles大于1时,
singSongFor()
的每次调用都会递归调用
singSongFor()
,直到NumberOfBaggles为0为止。此时,
printf(“墙上没有更多的啤酒瓶了。\n\n”)
将到达,该函数将完成,传递给调用函数,调用函数的参数为1,然后到达
printf(“将一瓶放在回收箱中,桶中有%d个空瓶。\n”,NumberOfBatters)和自身完成,返回
singSongFor(2)
。。。依此类推,直到你回到原来的数字,本例中为4。

只需一张简单的图片即可解释:


迭代可以有所帮助,这是一个很好的例子,但有时我认为从更高的层次理解递归也更容易,尤其是当事情变得更复杂时。例如,访问一棵树,或者特别是编写一个递归解析器,将其视为“tree.child是一棵树”,这会使代码比反复遍历更容易理解(无论如何,在我看来)。@AdamD.Ruppe:但是在这种情况下,如果您遍历了它,就不需要解释了。因此,递归的概念总是包含一个堆栈作为数据结构,还是它只是一种解释向后-向前循环机制的方法?堆栈非常清晰。这适用于任何编程语言吗?是的,它们始终是一个堆栈(某种类型的-在某些情况下,优化可能会改变这一点))在那里。调用另一个函数时,需要保存外部函数的状态,以便在内部函数完成后可以恢复。这是用任何语言的堆栈完成的(同样,除非它得到不同的优化,但它仍然以相同的方式工作)。