递归、调用栈及尾递归
无论是否递归调用,当在一个函数(外层函数)的运行期间调用另一个函数(被调用函数,即内层函数)时,在运行被调用函数之前,系统需要先完成3个操作,即:
从被调函数返回调用函数(外层函数)之前,系统还要完成3个操作,即:
当有多个函数构成嵌套调用时,按照"后调用先返回"的原则,上述函数之间的信息传递和控制转移必须通过"栈"来实现,每当调用一个函数时,就在栈顶为它分配一个存储区,每当退出一个函数时,就释放它的存储区,当前正在运行的函数的数据区必在栈顶。 递归函数 的运行过程类似于多个函数的嵌套调用,只是调用和被调用函数是同一个函数。
函数调用时,需要在栈中分配新的帧,将 返回地址 , 调用参数 和 局部变量 入栈。所以递归调用越深,占用的栈空间越多。
为了解决递归的开销大问题,使用尾递归优化,具体分两步:
使用尾递归的好处:因为进入下一层函数不再需要参考外层函数的信息,因此没必要保存外层函数的栈信息,递归需要用的stack只有目前这层被调用函数的,因此避免了栈溢出风险。
一些语言提供了尾递归优化特性,当识别出使用了尾递归时,会相应地只保留当前层函数的stack,节省内存,不会发生stackOverflowException调用栈溢出。
注意:
JVM缺乏尾调用指令,java编译器对尾递归的优化未实现,所以在java中尽量避免过深的递归调用,如果需要使用递归,建议优化成迭代式。 *
尾递归就是操作的最后一步是调用自身的递归。注意,尾递归的判断标准是函数运行 最后一步 是否调用自身,而不是是否在函数的最后一行调用自身。
这个不是尾递归:
这个是尾递归:
参考:
[1]. 什么是尾递归
[2]. 尾调用优化
[3]. 栈是如何实现递归的:递归与栈一些不得不说的事
[4]. stack 的三种含义