尾递归优化

关于循环优化的理论比较复杂,我们现在暂时切换到一些简单些的优化吧
我们经常在写代码时会使用递归调用,我们知道递归调用通常代价比较大,
但是递归调用比较符合我们的思考方式,写代码比较容易。
其实实际上对于一种比较特殊的递归调用,编译器可以直接帮助我们做优化,
将递归调用直接优化为非递归调用,这一类递归调用在编译器里面称为尾递归调用
(Tail Recursive Call).
比如函数
  1. int primes(int a, int b){
  2.     if(a==0)return b==1;
  3.     if(b==0)return a==1;
  4.     return primes(b,a%b);
  5. }
复制代码
像上面的一个代码,只有在代码的最后一行调用函数自身的代码称为尾递归,大部分现代
的编译器都能够对上面的代码进行优化,将递归优化成一个循环,因为其做法只需要修改所有
函数输入值,然后跳到函数入口就可以了。
有些尾递归调用可能不是很容易看出来,比如
  1. int primes(int a, int b){
  2.     if(a==0)return b==1;
  3.     if(b==0)return a==1;
  4.     if(a<b){
  5.         return primes(a,b%a);
  6.     }else{
  7.         return primes(a%b,b);
  8.     }
  9. }
复制代码
虽然上面代码有两次递归调用,但是两次都是尾递归,它们分别在两个不同分支的最后面。
此外另外一种比较常见的尾递归调用形式是:
  1.    double honic(int n){
  2.         if(n==1)return 1.0;
  3.         else return 1.0/n+honic(n-1);
  4.    }
复制代码
这里,递归调用在一个表达式中间,虽然它是最后一次计算(+操作)的一个参数,但是由于加法
满足结合率,我们可以改变所有累加的顺序,所以上面的编译器也可以自动转化为循环。
同样,计算
  1.    double honic(int n){
  2.         if(n==1)return 1.0;
  3.         else return honic(n-1)+1.0/(n*n);
  4.    }
复制代码
编译器也可以自动优化,(虽然后面还有计算1.0/(n*n),但是编译器可以自动优化,加法满足交换率).
像下面代码
  1.    double Fib(int n){
  2.         if(n<=1)return 1.0;
  3.         else return Fib(n-1)+Fib(n-2);
  4.    }
复制代码
这个代码中,两次递归调用中,只有后面那次调用(Fib(n-2))可以转化为循环的一部分,但是前面那次递归调用无法转化。
所以对这样的代码,最好还是通过手工优化。(其实,这里的函数Fib不含有任何指针之类会引起歧异的代码,从理论上,
编译也应该能够优化掉,但是实际上现在通常的编译器不会做这样的优化)

你可能感兴趣的:(compiler)