【问题标题】:Recursion flow in c language and how the output is printedc语言中的递归流程以及如何打印输出
【发布时间】:2019-01-25 08:03:18
【问题描述】:

我被打印出来的价值如何?

我知道递归一次又一次地调用自己。据我说,该函数应该返回空,因为该函数在打印之前被调用。 printf 是如何工作的?

recur(int i)
{
  if(i<0)
    return 0;
  recur(--i);
  printf("%d",i);
  recur(--i);
}

main()
{
  recur(2);
}

The output of this program is 
-1
0
1
-1

有人能解释一下它是如何工作的吗?

【问题讨论】:

  • 我最好的建议是在纸上调试你的代码,同时编写“调用堆栈”(这样你就可以准确地理解它的作用)。书面解释不如自己面对它有效。
  • 您的代码不是有效的 C,我怀疑它是产生输出的代码,printf 格式字符串中至少缺少\n,所以请始终发布minimal reproducible example 和连贯的东西.
  • 好吧,你必须添加一个#include &lt;stdio.h&gt; 并包含另一个return 0;(并忽略int 警告的默认值..)
  • 我的算法老师总是说不要尝试在纸上“调试”代码,因为它可以在小示例中工作,但大递归示例会导致“精神崩溃”。只要你有停止条件、递归条件和返回,你应该能够调试它并查看值在变化时
  • 大约 20 年前,隐式 int 规则已从 C 语言中删除。

标签: c recursion


【解决方案1】:

要了解会发生什么,您必须了解递归的工作原理。每个递归函数都需要一个测试条件来中断递归和递归调用。您将if (i &lt; 0) 作为您的测试条件,然后您有两个递归调用,它们之间有printf

那么它是如何工作的?

您可以考虑递归和结束,直到触发退出条件 - 然后在递归调用返回时展开。让我们看看这里是如何工作的。

递归

当您在main 中以recur(2) 开始时,逻辑采用什么路径来结束退出条件?

如果您简化函数并专注于在满足您的测试条件之前发生的事情,您会得到类似的结果:

void recur (int i) {
    if (i < 0)
        return;
    recur (--i);
    /* comes after return */
}

让我们一起来看看吧。在第一次调用时,i = 2 所以 --1 pre decriment 发生,使 i = 1, recur (1) 执行。下一次调用i = 0,再次调用recur (0)。最后是i = -1,第一个recur(-1)出现,所以if (i &lt; 0)执行,return被调用。

现在呢? -- 展开...

看看上面的缩短函数。当您return 时,您将从上次调用recur(--i) 返回,因此现在将控制权传递给第一个recur(-1) 之后的下一个命令(如上图/* comes after return */)那么完整函数中的下一个命令是什么?

    printf("%d\n", i);
    recur (--i);

第一次发生这种情况时i 的值是多少? (提示:-1)。

所以printf 被调用,然后接下来会发生什么?你的第二个recur(--i)。那里有i = -2if (i &lt; 0) return; 被触发,你从第二个recur(-2) 中解脱出来——没有命令跟随,函数调用现在完成了。

控件现在展开到上一个调用中,其中 i 现在是 0,调用 printf 并使用 i = -1 输入第二个 recur(--i),它再次点击 return,然后您展开一个到i = 1 从第一个recur(--i) 调用再次返回的位置更高,1 被输出。

第二个recur(--i) 调用在递减i = 0 之后输入,你现在再次递归到第一个i = -1 再次触发return,这导致控制传递给printf,最后一个-1打印。

您现在递归到第二个 recur(--i)i = -2 触发 return 并完成函数调用和递归。

因此,如果您通过两次递归调用返回对递归函数的控制,并查看每次达到 printf 的时间,您就会得到输出 -1, 0, 1, -1

仍然认为具有多个递归调用的递归函数是个好主意吗?

(如果您是阿司匹林推销员,他们是 - 否则,最好避免他们,除非他们是您唯一合理的选择)

解决此类问题的唯一方法是 (1) 与您的调试器交朋友并单步执行您的代码,保留一份草稿单,其中记录了何时输入了哪个调用,或者 (2) 复制并粘贴该函数向下跟踪每个级别的递归调用的状态并逐步进行。

始终是一个很好的学习体验,但如果有可用的程序解决方案,则逻辑会更加直接,并且您可以避免每次递归调用的函数调用开销。递归函数有它们的位置,并且它们提供了非常优雅的解决方案来解决一些问题。但是您必须注意每次调用都会为局部变量分配一个单独的函数堆栈和空间时,递归会发生多少次——这可能会导致 StackOverflow。

【讨论】:

    【解决方案2】:

    当你打电话给recur(2);,那么此时

    recur(--i);
    

    recur(1) 被调用,然后当它再次到达那个点时,recur(0); 被调用,然后再次在那个点上,recur(-1); 被调用。 recur(-1); 调用立即返回,因为 if(i&lt;0) return 0;

    recur(0); 调用,其中 i 现在是 -1,然后执行打印并返回(它再次执行 recur(--i);,但此时是 -2)。接下来,recur(1) 调用(其中 i 现在是 0)进行打印,并且它也命中 recur(1),然后返回。

    最后,原始的recur(2); 调用(其中i 为1)进行打印并调用另一个recur(0);,正如我们所见,导致-1 被打印。这将导致-101-1 的输出,或者如果您在每次打印中添加一个换行符,则会得到您所看到的输出。

    顺便说一句,我建议把你的主要功能写成

    int main()
    {
        recur(2);
        return 0; // return value of 0 indicates successful program execution
    }
    

    并将recur(int i) 的签名更改为void recur(int i) 并将其return 0; 更改为return;

    根据我的说法,函数应该返回空,因为函数是 打印前调用。 printf 是如何工作的?

    总而言之,它之所以有效,是因为并非所有递归调用都在进行进一步的递归调用。它们只在特定条件下进行递归调用,如果它们不进行递归调用,它们会在某个时间点结束,从而允许调用它的函数继续执行。

    【讨论】:

      【解决方案3】:

      问题是你在打印出值之前调用了你的函数。

      recur(int i)
      {
      if(i<0)
          return 0;
      recur(--i);
      printf("%d",i);
      recur(--i);
      }
      

      您在main 函数中调用recur(2),第一个if(i&lt;0) 不返回0,因为i=2。然后你的函数 recur 被调用,值为 1 recur(1)

      在这种情况下,if(i&lt;0) 也不会返回 0,因为 i=1 比您的函数 recur(0) 调用时使用 0。

      if(i&lt;0) 仍然没有返回 0,因为 i=0 并且你的函数是用 recur(-1) 调用的。

      那你是if(i&lt;0) returns 0 因为i=-1

      现在我们开始print() 取出我们之前给函数的值。 这是print(-1),因为它是我们函数的最后一个值。

      然后我们用recur(-2)再次调用recur,if返回0。

      然后我们print(0),因为那是我们“输入”的第二个值是 0。

      然后我们用recur(-1)调用recur,if(i&lt;0)返回0。

      这会持续到下一个值。

      对于未来的问题,我建议您使用调试器。以 GDB 调试器为例。

      这是一个开始使用 gdb 的例子: GDB Start guid

      我希望这对你有帮助。

      【讨论】:

        【解决方案4】:

        还要考虑

        您使用的变量是自动变量 auto 变量的值受括号限制,因此程序将打印控制流当前所在的括号内可用的 i 值

        【讨论】:

          【解决方案5】:

          当使用参数 X &gt;= 零调用函数时,您将执行以下行:

          recur(X-1)
          print X-1
          recur(X-2)
          return
          

          当使用参数 X &lt; 零调用函数时,您只会执行此行:

          return
          

          现在,如果您为每个调用级别编写带有缩进的函数调用:

          recur(2)
                  recur(1)
                          recur(0)
                                  recur(-1)
                                          return
                                  print -1
                                  recur(-2)
                                          return
                                  return
                          print 0
                          recur(-1)
                                  return
                          return
                  print 1
                  recur(0)
                          recur(-1)
                                  return
                          print -1
                          recur(-2)
                                  return
                          return
                  return
          return
          

          删除除打印之外的所有内容:

                                  print -1
                          print 0
                  print 1
                          print -1
          

          所以输出是:

          -1
          0
          1
          -1
          

          【讨论】:

            【解决方案6】:

            不要使用recur(--i),请使用recur(i-1)

            使用--i,它实际上会减少i,因此在以后的printf 中,它会是可见的(错误地)。

            例如。如果 i0 并且你这样做 recur(--i) ,它会将 i 的实际值减少到 -1 然后调用 recur()printf 稍后将打印 -1

            使用recur(i-1)i 作为0 它将以-1 作为参数调用,但i 的实际值不会改变。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-02
              • 1970-01-01
              • 2015-01-28
              • 1970-01-01
              相关资源
              最近更新 更多