尾递归尾递归自然也是一种递归的形式,百度百科的定义是:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。(注意红字)
还是那阶乘来举个例子,虽然阶乘最好的解法不是递归。
下面的是普通的递归代码
int func(int n)
{
if(n<=1) return 1;
return func(n-1)*n;
}
int main()
{
int a[3];
func(5);
return 1;
}
下面来看看它的汇编代码
下图是调用的fun函数中的汇编代码,如图中绿圈所示,每一次得到的值都会放到rbp中,再在再次调用时被压入栈中,最后当n==1时在一次一次出栈,这也是为什么说递归比较消耗空间的原因
接下来猪脚登场,尾递归,尾递归上面已经阐述了,就是让我的表达式中不含返回值,这样的话计算机就不需要把你的返回值放入栈中了。到底编译器会不会又把你的这个不需要东西放入栈中呢?
我试了试(用的dev),它的优化方式是这样的如果你栈的空间够用的话,我就不管你用不用都给你存进去,如果空间不够不够用的话,我就不会存。在不停的调用函数时,后一个函数也会的直接把后一个函数的覆盖掉,而不是又重新开辟空间储存这个函数。
int tail_func(int n, int res)
{
if (n <= 1) return res;
return tail_func(n - 1, n * res);//将需要的东西传入,而不是在表达式上加上一个函数的返回值
}
int main()
{
int a[3];
tail_func(5,1);
return 1;
}
下面是汇编代码:
并且远方亲戚也确实进行了不断存储的操作
现在我们把数据扩大,再不考虑溢出的情况下:
int main()
{
int a[1024*1024];
tail_func(1024*1024,1);
return 1;
}
正如下图所示,这次程序没有再去调用远方亲戚了,而是把远方亲戚请了过来,并且也没有push,pop等压栈入栈的操作达到对空间的重复利用