这是一个获取数字位数之和的函数:
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 )
我是编程新手,所以请原谅任何错误
答案 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
变量。所以这在精神上大多是正确的。
如果这是正确的,那么当我使用递归时有什么好处 可以使用循环在正常函数中执行它,因此只覆盖一个 记忆位置?
有几个原因,包括:
我没有看到递归的方式(除了返回的最后一个 0)返回值而不取消注释第3行。
注释第3行是未定义的行为。为什么要这样做?
答案 2 :(得分:1)
是的,参数和局部变量是每个invokation的本地变量,这通常是通过创建程序堆栈上设置的每个invokation变量的副本来实现的。是的,与具有循环的实现相比,这会消耗更多内存,但前提是问题可以通过循环和常量内存使用来解决。考虑遍历树 - 您必须将树元素存储在某处 - 无论是在堆栈上还是在其他结构中。递归优势是它更容易实现(但并不总是更容易调试)。
如果你在第二个分支中评论return sum;
,行为是未定义的 - 任何事情都可能发生,包括预期的行为。那不是你应该做的。