执行递归函数时堆栈调用等等会发生什么?递归甚至首先使用堆栈吗?我希望得到一个答案,有助于更好地可视化递归期间发生的事情。
答案 0 :(得分:1)
通常,递归调用与任何其他函数调用相同。它创建一个新的堆栈帧,保存旧变量并最终返回给调用者,就像任何旧的函数调用一样。这意味着递归函数可能导致堆栈溢出。 (事实上,这可能是溢出堆栈的最简单方法!)
但是,在某些语言中,尾递归有一个例外。尾递归涉及递归调用,这是函数执行的最后一项操作(即在尾部位置中调用)。这意味着除了直接返回之外,函数不能对递归调用的结果执行任何。比较这两个愚蠢的例子:
// Not tail-recursive: we add 1 to the result of foo()
function foo(x) {
if (x > 0) {
return 1 + foo(x - 1)
} else {
return 0;
}
}
// Tail recursive: we return foo() directly
// (`x - 1' happens *before* foo is called)
function foo(x) {
if (x > 0) {
return foo(x - 1);
} else {
return 0;
}
}
如果函数是尾递归的,那么在每次迭代时都没有分配堆栈帧的意义,因为不需要保留任何信息。相反,可以重用现有的堆栈帧,或者可以将整个事物重写为循环。
像Scala这样的语言可以做到这一点,这意味着您可以以递归方式编写迭代过程,而不会遇到堆栈溢出。
然而,递归确实没有什么特别之处。如果函数调用处于尾部位置,即使它是对不同函数的调用,我们也不需要堆栈。我们可以将尾调用实现为跳转。这是由某些语言(如Scheme)强制执行的,但由于Java兼容性原因,无法在Scala中实现。 正确的尾部调用对于实现相互递归和continuation passing style非常重要,而不必担心堆栈溢出。
所以,除了某些语言只能在尾部位置优化 direct 递归,而不是尾部调用时,递归调用与普通调用没有任何根本的特殊关系。