【问题标题】:Interpreting this recursive function (in C)解释这个递归函数(在 C 中)
【发布时间】:2013-06-14 21:52:33
【问题描述】:

我无法理解这个反向函数的工作原理。我已经尝试在纸上一步一步地弄清楚代码的作用,但这对我来说没有意义。我对代码所做的最好(尽管很粗略)的解释是:

http://s7.postimg.org/632xhovwr/recursion_confusion.png

#include <stdio.h>
#include <string.h>
#define MAX 1000

void reverse(char s[]);

main()
{
    char str[] = "remotes";

    printf("Before: %s\n",str);
    reverse(str);
    printf("After: %s\n",str);

    system("Pause");
    return 0;
}

void reverse(char s[])
{
    static int i = 0, n;
    char c = s[i];

    if (c != '\0') {
        ++i;
        reverse(s);
        s[n-i] = c;
        --i;
    } 
    else {
        n = i;
    }
}

当递归是代码的最后一步时,通常我不会遇到递归函数的问题,因为您应用递归直到某些终止条件和某种向后级联。但是当递归调用前后都有代码时,事情就变得更加混乱了。

【问题讨论】:

  • 我不确定为什么这个函数甚至需要递归。您所要做的就是从第一个字符循环到字符串的一半,将每个字符与其另一半的镜像交换。
  • @RobertHarvey 可以作为递归练习吗?
  • @vidit 不,它是static,并且在到达0终止符时设置之前没有使用。
  • @RobertHarvey Daniel Fischer 所说的;这是基于 K&R 的一个练习。我知道不用递归定义更简单。
  • 递归函数通过static 变量在调用之间传递状态,因为它不想弄脏它的公共接口。真的很优雅。

标签: c recursion


【解决方案1】:

您在纸上的分析是正确的 - 诀窍是 ni 是静态的(每个堆栈帧都引用同一个变量实例)而 c 分配在堆栈上(所以每个堆栈帧有自己的副本)。

将一些值代入您的分析中,并使用cc'c''c''' 来表示c 的不同堆栈帧实例:

// original function call: stack frame 0
c = str[0]
i = 1

 // stack frame 1
 c' = str[1]
 i = 2

  // stack frame 2
  c'' = str[2]
  i = 3

   // stack frame 3
   c''' = str[3]
   i = 4

    // stack frame 4
    n = 4

   // unwinding: stack frame 3
   str[0] = c'''
   i = 3

  // unwinding: stack frame 2
  str[1] = c''
  i = 2

 // unwinding: stack frame 1
 str[2] = c'
 i = 1

// unwinding: stack frame 0 (original function call)
str[3] = c
i = 0

编辑:解决您的评论:不要放弃理解递归!一旦你掌握了它并且可以“递归思考”,就很容易编写让你看起来比你更聪明的代码(一项非常有用的技能!)。例如,对 reverse() 函数进行小的重构可以创建一个更小的版本:

void reverse(char s[])
{
    static int i = 0;
    char c = s[i++];
    if (c) {
        reverse(s);
        s[i++] = c;
    }
    i = c ? i : (int)c;
}

真的,原作者通过比必要的更冗长来帮助你:-)

【讨论】:

  • 这很好地解释了它,谢谢!另一个问题是,创建者是如何想出这段代码的?它是如此紧凑和出色,我自己永远不会想出这样的东西:(
  • 我不一定会说代码很棒,除非在递归教学的上下文中:-)。话虽如此,这是使用递归对列表进行反向迭代的常见模式的一种变体。
【解决方案2】:

您必须考虑到 i 和 n 是静态变量,这意味着函数将在调用之间保持值。

这是这些值的输出:

i: 1/ n: 0
  i: 2/ n: 0
    i: 3/ n: 0
      i: 4/ n: 0
        i: 5/ n: 0
          i: 6/ n: 0
            i: 7/ n: 0

当c变量提示NULL值时,函数更新n值并开始反转字符串:

            i: 7/ n: 7/ s: semotes
          i: 6/ n: 7/ s: semotes
        i: 5/ n: 7/ s: setotes
      i: 4/ n: 7/ s: setotes
    i: 3/ n: 7/ s: setomes
  i: 2/ n: 7/ s: setomes
i: 1/ n: 7/ s: setomer

【讨论】:

    【解决方案3】:

    在这里,i 是静态 int,通过递归调用不断递增,直到 s[i] == '\0'。 s[i] 的值也保存在调用帧中的局部变量“c”中

    当 n == i 时,会发生相反的情况,局部变量 'c' 的值会随着 i 递减而写回

    基本上,跨调用帧的变量“c”的副本会临时保存这些值。

    【讨论】:

      【解决方案4】:

      为了解释这些步骤,我指出了三个断点 A、B 和 C。下表显示了每个阶段的变量。

      void reverse(char s[])
      {
          static int i = 0, n;
          char c = s[i];
      /* A */
          if (c != '\0') {
              ++i;
      /* B */
              reverse(s);
      /* C */
              s[n-i] = c;
              --i;
      /* D */
          } 
          else {
              n = i;
      /* E */
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-04
        • 2018-05-31
        • 2016-10-04
        • 1970-01-01
        • 2018-03-19
        • 2022-11-10
        • 2022-07-04
        • 1970-01-01
        相关资源
        最近更新 更多