对C语言中的递归感到困惑

对C语言中的递归感到困惑,c,recursion,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 b

我有以下代码:

#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;
}
#包括
void SingSongFor(整数瓶){
如果(NumberOfLabels==0){
printf(“墙上再也没有啤酒瓶了。\n\n”);
}否则{
printf(“%d瓶啤酒挂在墙上。%d瓶啤酒。\n”,numberof瓶装,numberof瓶装);
int oneless=瓶数-1;
printf(“取下一瓶,在墙上传%d瓶啤酒。\n\n”,少一瓶);
SingSongFor(oneless);//此函数调用自身!
//在函数结束前打印消息
printf(“将一个瓶子放在回收箱中,%d个空瓶子放在回收箱中。\n”,numberof瓶);
}
}
int main(int argc,const char*argv[]{
//我们可以唱99首诗,但4首更容易思考
SingSongFor(4);
返回0;
}
据我所知,程序必须在打印以下内容后终止:

墙上再也没有啤酒了

但它为什么会继续打印:

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

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

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

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

if函数已经打印出了一条消息,但是它没有终止消息,而是同时到达else。这怎么可能?“NumberOfVillages”是如何从1增加到4的呢

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

行后

 SingSongFor(oneFewer); // This function calls itself
你有一张打印纸

 printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",numberOfBottles);
这就是程序所做的。具有存储在堆栈上的
numberoflabels
值。

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

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

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

SingSongFor(4) | v SingSongFor(3) | v SingSongFor(2) | v SingSongFor(1) | v SingSongFor(0) SingSongFor(4) | v SingSongFor(3) | v SingSongFor(2) | v SingSongFor(1) | v SingSongFor(0)
最后一次呼叫显示没有更多瓶子,然后返回,这就是神奇的发生:它返回到上一次呼叫,然后打印关于回收箱的消息,然后返回到上一次呼叫,再次打印消息,依此类推。

一切正常。当您到达最后一条消息
时,墙上已经没有啤酒瓶了。
您的程序返回到调用它的位置(它是在带有参数
1
的函数
SingSongFor
中调用的)。然后打印消息
将一个瓶子放入回收箱,将一个空瓶子放入垃圾箱。
并返回上一次调用函数
SingSongFor
,参数为
2
。如图4所示。

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

首先,让我们研究一个替代程序,它使用非递归函数调用,执行与您的程序基本相同的操作

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()
的两行,然后调用
SingSongFor3
。这将打印它的2行,然后调用
SingSongFor2()
,它将打印它的行,依此类推
SingSongFor1()
不调用任何其他函数,因此它会打印所有三行,然后返回到
SingSongFor2()。当您按照函数调用“down”时,总共会在墙上看到8行
X个瓶子/取下一个
,然后在反向返回“up”时看到4行“将瓶子放入垃圾箱”

你的函数没有什么不同,只是它被参数化了,并添加了一些逻辑来确定它什么时候应该像
SingSongFor1()
一样工作,什么时候应该像其他3个一样工作。我说这没有什么不同,只是在你的例子中,你有一个程序文本的副本,它由程序的每次调用共享,而不是4个单独的(几乎相同的)文本副本。使共享文本副本成为可能的是每个函数调用的本地上下文—参数、变量和一些有关程序所在位置和程序执行状态的内务管理信息

通常,此上下文信息包含在称为堆栈的特殊数据结构中。之所以称之为堆栈,是因为你把东西一个叠在另一个上面,然后从“顶部”一次移除一个。每个堆栈帧包含函数一次调用的上下文:参数——在你的例子中是
numberoflabels
;局部变量-
oneless
;以及有关函数结束或返回时应执行哪个语句的信息。调用函数时,与该调用对应的帧被推送到堆栈上,并执行函数的文本。当它完成时,框架弹出,调用函数中的执行在其停止点(存储在弹出的堆栈框架中用于此目的)恢复。它将使用堆栈的新“顶部”框架恢复其上下文

不过,重要的是,递归函数的工作方式与任何其他函数完全相同——每次调用它时,它都会获得自己的新上下文,即使函数的文本是相同的。它执行到完成,然后返回到上一个上下文-可能是同一个函数,但具有不同的
    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!
#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.
    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);