用小例子理解递归

时间:2016-07-25 18:26:48

标签: c++ recursion

您好我在cpp中写过这样的小递归方法。我想了解递归

void print(int n)
{
    if(n==6)
        return;

    print(++n);
    cout<<n<<endl;
    //output is 6 5 4 3 2

}
void print(int n)
{
    if(n==6)
        return;

    print(n+1);
    cout<<n<<endl;
    //output is  5 4 3 2 1

}

void print(int n)
{
    if(n==6)
        return;

    print(n++);
    cout<<n<<endl;
    //programme crash

}

你能否解释一下我内心发生了什么?

3 个答案:

答案 0 :(得分:2)

函数调用放在堆栈上。把它想象成一堆盘子。任何时候在你的代码中调用“print(x)”,它都是这堆板的另一个补充。当函数到达其结束的大括号或当它返回return语句时,它将从堆栈中删除。

我假设你在这些函数上调用print(0)。因此,print(0)是堆栈中的第一件事。你的上一个函数崩溃了,因为它“永远”地调用了print(0),直到它没有空间容纳更多的“板块”。这称为无限递归,由于堆栈的限制,无限递归很少是无限的。

至于其他函数,嗯,“cout”语句只被称为“after”函数从堆栈中删除。这些方法中的每一种都不断地将新东西放在堆栈上,只有一个例外是print(6)调用。这通常称为基本情况,并且是递归过程的结束。因为它开始从堆栈中逐步移除板块,这允许发生所有cout语句(与无限递归的情况不同)。

要理解这些代码的不同之处,你必须确保理解n ++,++ n和n + 1之间的区别。

答案 1 :(得分:1)

这不像递归中的问题那样是表达式评估中的问题。当n达到6时,我们有三个例程停止;否则,他们会做某种形式的增量和重复,表达方式不同。递归调用后,打印 n 的本地值并返回给调用者。

请注意,每次调用 print 时,都会向运行时堆栈添加新的本地变量空间。每个都有自己的 n 副本:递增一个不会改变其他副本。

  • ++ n 增加 n 的本地值并再次调用该函数。
  • n + 1 请勿更改 n 的本地副本,但请使用下一个更高的值再次调用该函数。
  • ++ n 使用 n 当前值再次调用该函数;一旦返回,请增加 n 的本地值,然后打印。

答案 2 :(得分:0)

有时候,尝试说明呼叫顺序会很有帮助。

想象一下函数f1到f7的序列,其中f1调用f2调用f3,依此类推f7。 (因为我从0开始,而不是1)

f1看起来像:(和其他非常相似)

void f1(int n)
{ 
   if(n==6)
      return;
   f2(++n); 
   std::cout<<n<<std::endl;
}

因此,由f1(0)启动的调用序列可能如下所示:

f1(0)--v                : because f2 is called with ++n
       f2(1)--v         : because f3 is called with ++n etc.
              f3(2)--v
                     f4(3)--v
                            f5(4)--v
                                   f6(5)--v
                                          f7(6)

每个函数调用都有一个返回:

                                          // no cout of 7
                                   return to f6(6)  
                                   cout<<... n is 6
                            return to f5(5)
                            cout<<... n is 5
                     return to f4(4)
                     cout<<... n is 4
              return to f3(3)
              cout<<... n is 3
       return to f2(2)
       cout<<... n is 2
return to f1(1)
cout<<... n is 1

现在,要转移到使用递归,只需用foo

替换编号的f
void foo(int n)
{ 
   if(n==6)
      return;
   foo(++n); 
   std::cout<<n<<std::endl;
}

和调用序列(用foo(0)启动)可能看起来像

foo(0)--v               -- given 0, calls foo with 1
        foo(1)--v              -- give 1, calls foo with 2
                foo(2)--v             -- etc
                        foo(3)--v
                                foo(4)--v
                                        foo(5)--v
                                                foo(6) - return

我打电话给第一部分递归^^^^^^^^^^^^^^^^^^^^^^^^

                                        return to foo(6)  (was 5)
                                        cout<<... n is 6
                                return to foo(5)  (was 4)
                                cout<<... n is 5
                        return to foo(4)  (was 3)
                        cout<<... n is 4
                return to foo(3)  (was 2)
                cout<<... n is 3
        return to foo(2)  (was 1)
        cout<<... n is 2
return to foo(1)  (was 0)
cout<<... n is 1

第二部分,所有这些函数的返回,这个特定堆栈使用的“崩溃”,我有时会注意到'decursion'(旧军事术语的新用法)。 (++运算符的影响使得对齐误导。)

因为cout在递归调用之后(即在递归中),即使输入'n'在递归期间增加,输出序列也在减少。

因此,这个特定的序列cout每行1位数:

6
5
4
3
2
1

更新 - 为什么最后一段代码会崩溃?

最后一个代码片段崩溃,因为在递归调用之后发生了后递增。所以调用foo(0)会调用foo(n ++),但这只是再次调用foo(0),然后再次增加n(再次)。第二个和所有后续的foo()递归都看到相同的值0,因此终止条件(n == 6)永远不会发生,并且堆栈溢出了无限函数调用。