关于C语言中递归函数的几个问题

时间:2013-06-04 10:14:40

标签: c recursion scope

这是一个获取数字位数之和的函数:

int sumOfDigits(int n)
{
    int sum=0; //line 1
    if(n==0)
        return sum;
    else
    {
        sum=(n%10)+sumOfDigits(n/10); //line 2
        // return sum;  //line 3
    }
}

在编写此代码时,我意识到局部变量的范围对于函数的每个单独递归都是局部的。我是否正确地说,如果n=11111,每次递归都会创建5个sum变量并将其推送到堆栈中?如果这是正确的,那么当我可以使用循环在正常函数中执行它时使用递归有什么好处,从而只覆盖一个内存位置?如果我使用指针,递归可能会占用与普通函数类似的内存。

现在我的第二个问题,即使这个函数每次都给我正确的结果,我也看不到递归(除了返回0的最后一个)递归值而没有取消注释第3行。(使用gany使用geany )

我是编程新手,所以请原谅任何错误

3 个答案:

答案 0 :(得分:4)

  

所以我说得对,如果n = 11111,每次递归都会创建5个和变量并推送到堆栈上?

从概念上讲,编译器可能会将某些形式的递归转换为跳转/循环。例如。执行尾调用优化的编译器可以转为

void rec(int i)
{
    if (i > 0) {
        printf("Hello, level %d!\n", i);
        rec(i - 1);
    }
}

等同于

void loop(int i)
{
    for (; i > 0; i--)
        printf("Hello, level %d!\n", i);
}

因为递归调用位于尾部位置:调用时,rec的当前调用除了调用者return之外没有其他工作要做,所以它也可以重复使用它的堆栈帧进行下一次递归调用。

  

如果这是正确的,那么当我可以使用循环在正常函数中使用递归时,使用递归有什么好处,从而只覆盖一个内存位置?如果我使用指针,递归可能会占用与普通函数类似的内存。

对于这个问题,递归是非常不合适的,至少在C中,因为循环更具可读性。但是,有些问题,递归更容易理解。树结构的算法是最好的例子。

(尽管每个递归都可以通过具有显式堆栈的循环来模拟,然后可以更容易地捕获和处理堆栈溢出。)

我不理解关于指针的说法。

  

我没有看到递归(除了返回0的最后一个递归)如何在不取消注释第3行的情况下返回值。

偶然。程序显示未定义的行为,因此它可以做任何事情,甚至返回正确的答案。

答案 1 :(得分:2)

  

所以我说得对,如果n = 11111,则创建5个和变量   并在每次递归时推入堆栈?

递归是5级深度,因此传统上最终将创建5 stack frames(但在下面阅读!),其中每一个都有空间来容纳sum变量。所以这在精神上大多是正确的。

  

如果这是正确的,那么当我使用递归时有什么好处   可以使用循环在正常函数中执行它,因此只覆盖一个   记忆位置?

有几个原因,包括:

  • 递归表达算法可能更自然;如果性能可以接受,可维护性很重要
  • 简单的递归解决方案通常不会保持状态,这意味着它们可以简单地并行化,这是多核时代的一个主要优势
  • compiler optimizations经常否定递归的缺点
  

我没有看到递归的方式(除了返回的最后一个   0)返回值而不取消注释第3行。

注释第3行是未定义的行为。为什么要这样做?

答案 2 :(得分:1)

是的,参数和局部变量是每个invokation的本地变量,这通常是通过创建程序堆栈上设置的每个invokation变量的副本来实现的。是的,与具有循环的实现相比,这会消耗更多内存,但前提是问题可以通过循环和常量内存使用来解决。考虑遍历树 - 您必须将树元素存储在某处 - 无论是在堆栈上还是在其他结构中。递归优势是它更容易实现(但并不总是更容易调试)。

如果你在第二个分支中评论return sum;,行为是未定义的 - 任何事情都可能发生,包括预期的行为。那不是你应该做的。