是否有任何c编译器向我显示递归函数的每个步骤?

是否有任何c编译器向我显示递归函数的每个步骤?,c,recursion,C,Recursion,目前我正在用c语言编写一个快速排序算法(我是初学者),对我来说理解递归非常困难。这不是我的第一个使用递归函数的程序,因此我想知道是否有任何c编译器可以显示递归函数的步骤,以使其更简单。使用Visual Studio IDE,有一个免费版本可用。 您可以使用此IDE查看调用堆栈。 或者使用代码块IDE虽然您可以使用IDE逐步完成递归,但我发现对学生来说最有启发性的事情是在递归函数的开头使用printf。(实际上,最有启发性的任务是使用纸和笔手动执行递归函数,但这对于深度递归来说很快就会过时!) 例

目前我正在用c语言编写一个快速排序算法(我是初学者),对我来说理解递归非常困难。这不是我的第一个使用递归函数的程序,因此我想知道是否有任何c编译器可以显示递归函数的步骤,以使其更简单。

使用Visual Studio IDE,有一个免费版本可用。 您可以使用此IDE查看调用堆栈。
或者使用代码块IDE

虽然您可以使用IDE逐步完成递归,但我发现对学生来说最有启发性的事情是在递归函数的开头使用printf。(实际上,最有启发性的任务是使用纸和笔手动执行递归函数,但这对于深度递归来说很快就会过时!)

例如:

int fibonacci(int n) {
    printf("fibonacci(%d)\n", n);
    if (n == 0 || n == 1)
    return n;
  else
    return (fibonacci(n-1) + fibonacci(n-2));
}
这将产生以下递归跟踪。不幸的是,对于双递归,这并不能清楚地说明什么叫做什么:

fib 4
fibonacci(4)
fibonacci(3)
fibonacci(2)
fibonacci(1)
fibonacci(0)
fibonacci(1)
fibonacci(2)
fibonacci(1)
fibonacci(0)
fibonacci(4)=3
如果不介意将递归计数添加到递归函数中,可以使用以下代码(作为示例)获得缩进良好的输出:

生成以下显示递归的输出

> fib 5 
fibonacci(5)
   -->fibonacci(4)
      -->fibonacci(3)
         -->fibonacci(2)
            -->fibonacci(1)
            -->fibonacci(0)
         -->fibonacci(1)
      -->fibonacci(2)
         -->fibonacci(1)
         -->fibonacci(0)
   -->fibonacci(3)
      -->fibonacci(2)
         -->fibonacci(1)
         -->fibonacci(0)
      -->fibonacci(1)
fibonacci(5)=5

对快速排序执行类似的操作,以创建类似的递归跟踪。

作为对优秀建议的扩展,我建议使用来帮助可视化。它适用于所有操作系统,并且完全免费。它以人类可读的文本(点语言)作为输入,并提供相当漂亮的图形作为输出

考虑以下斐波那契示例程序:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static inline int  unique_id(void)
{
    static int  id = 0;
    return ++id;
}

static int  dot_fibonacci_recursive(int n, int parent_id, FILE *out)
{
    const int  id = unique_id();

    if (n < 2) {
        printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>1</b> > ];\n", id, id, n);
        printf("    \"%d\" -> \"%d\";\n", id, parent_id);

        return 1;
    } else {
        int result1, result2;

        result1 = dot_fibonacci_recursive(n - 1, id, out);
        result2 = dot_fibonacci_recursive(n - 2, id, out);

        printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>%d</b> > ];\n", id, id, n, result1 + result2);
        printf("    \"%d\" -> \"%d\";\n", id, parent_id);

        return result1 + result2;
    }
}

int  fibonacci(int n)
{
    const int  id = unique_id();
    int        result;

    printf("digraph {\n");

    result = dot_fibonacci_recursive(n, id, stdout);

    printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>%d</b> > ];\n", id, id, n, result);
    printf("}\n");
    fflush(stdout);

    return result;
}

int main(int argc, char *argv[])
{
    int  n, fn;
    char dummy;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s N > graph.dot\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program will compute the N'th Fibonacci number\n");
        fprintf(stderr, "using a recursive function, and output the call graph\n");
        fprintf(stderr, "as a dot language directed graph.\n");
        fprintf(stderr, "\n");
        fprintf(stderr, "Use e.g. 'dot' from the Graphviz package to generate\n");
        fprintf(stderr, "an image, or display the graph interactively. For example:\n");
        fprintf(stderr, "       dot -Tsvg graph.dot > graph.svg\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (sscanf(argv[1], " %d %c", &n, &dummy) != 1 || n < 0) {
        fprintf(stderr, "%s: Invalid N.\n", argv[1]);
        return EXIT_FAILURE;
    }

    fn = fibonacci(n);

    fprintf(stderr, "%d'th Fibonacci number is %d.\n", n, fn);

    return EXIT_SUCCESS;
}
我们看到调用图 前面的数字标识对递归函数的调用(
dot\u fibonacci()
),最外面的
fibonacci()
调用是第一个调用。因为my
fibonacci()
只是一个不进行计算的包装函数,所以根节点始终与第二个节点相同(第一次实际调用
dot\u fibonacci()

生成上述图形的点语言文本为

digraph {
    "5" [ label=< 5. F<sub>1</sub> = <b>1</b> > ];
    "5" -> "4";
    "6" [ label=< 6. F<sub>0</sub> = <b>1</b> > ];
    "6" -> "4";
    "4" [ label=< 4. F<sub>2</sub> = <b>2</b> > ];
    "4" -> "3";
    "7" [ label=< 7. F<sub>1</sub> = <b>1</b> > ];
    "7" -> "3";
    "3" [ label=< 3. F<sub>3</sub> = <b>3</b> > ];
    "3" -> "2";
    "9" [ label=< 9. F<sub>1</sub> = <b>1</b> > ];
    "9" -> "8";
    "10" [ label=< 10. F<sub>0</sub> = <b>1</b> > ];
    "10" -> "8";
    "8" [ label=< 8. F<sub>2</sub> = <b>2</b> > ];
    "8" -> "2";
    "2" [ label=< 2. F<sub>4</sub> = <b>5</b> > ];
    "2" -> "1";
    "1" [ label=< 1. F<sub>4</sub> = <b>5</b> > ];
}
而不是
printf()
。仅在输出点语言内容时使用
printf(…)
fprintf(stdout…)
。通过在命令行中添加
>filename.dot
,可以将程序的标准输出重定向到文件中。

  • 开始你的方向图

     printf("digraph {\n");
    
    并以

     printf("}\n");
    
    边和节点介于两者之间。他们的顺序无关紧要

    (如果需要无方向图,请使用
    graph{
    ,并使用
    --
    作为边。)

  • 节点是使用
    “id”[…];
    定义的,其中
    id
    是用于寻址该节点的标识符,
    是以逗号分隔的属性列表

    对于结构化标签,您需要的典型属性是
    shape=“record”,label=“foo | bar”
    ;对于HTML格式标签,需要
    label=
    ;对于简单文本标签,需要
    label=“foo”
    。如果省略标签(或整个节点规范),则标签将使用
    id

    要使用指针可视化树、列表或任何内容,请使用

     printf(" \"%p\" [ ... ];\n", (void *)ptr);
    
    它使用实际指针值作为节点ID的源。

  • 有向边具有形状

    "source-id" -> "target-id";
    

    除颜色外,还可以使用
    taillabel
    headlabel
    等标记箭头


  • 这就是你所需要的一切,尽管你可以在网上找到更多的工具。

    不是编译器,而是调试器。有了现代化的IDE,它对用户很友好,你可以一步一步地看到执行情况,直到解决问题为止。记得在要求工具推荐时指定你正在使用的环境。非常感谢!我以前没有这样做过o写在纸上,但是因为我必须在程序运行之前测试好几次,所以我浪费了很多纸。非常感谢!我会尝试的。
     printf("digraph {\n");
    
     printf("}\n");
    
     printf(" \"%p\" [ ... ];\n", (void *)ptr);
    
    "source-id" -> "target-id";
    
    "source-id" -> "target-id" [ color="#rrggbb" ];