源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

C#中尾递归的使用、优化及编译器优化

  • 时间:2022-12-21 01:44 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:C#中尾递归的使用、优化及编译器优化
[b]递归运用[/b] 一个函数直接或间接的调用自身,这个函数即可叫做递归函数。 递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果。 递归最重要的是边界条件,这个边界是整个递归的终止条件。
[u]复制代码[/u] 代码如下:
static int RecFact(int x) {     if (x == 0)         return 1;     return x * RecFact(x - 1); } RecFact(10);
上面是个经典阶乘函数的实现。这里分2步: 1.转换,把10的阶乘转化成10*9!,10(9*8!)....每次转换规模就变的更小。 2.逼近,转换到最小规模时0!,求解1。开始逆向合并逐渐逼近到10,得出解。 这里的x==0就是我们的边界条件(即终止条件),也有的依赖外部变量为边界。 如果一个递归函数没有边界,也就无法停止(无限循环至内存溢出),当然这样也没什么意义。 RecFact调用堆栈: [img]http://files.jb51.net/file_images/article/201504/201541085204185.png?201531085216[/img] 常见使用场景: 1.阶乘/斐波那契数列/汉诺塔 2.遍历硬盘文件 3.InnerExceptions异常扑捉(exception.InnerException==null) [b]尾递归优化[/b] 当边界不明确的时候,递归就很容易出现溢出问题。 在阶乘过程中,堆栈需要保存每次(RecFact)调用的返回地址及当时所有的局部变量状态,期间堆栈空间是无法释放的(即容易出现溢出)。 为了优化堆栈占用问题,从而提出尾递归优化的办法。
[u]复制代码[/u] 代码如下:
static void TailRecursion(int x)     {         Console.WriteLine(x);         if (x == 10)             return;         TailRecursion(x + 1);     }     TailRecursion(0);
使用尾递归堆栈可以不用保存上次的函数返回地址/各种状态值,而方法遗留在堆栈上的数据完全可以释放掉,这是尾递归优化的核心思想。 由于尾递归期间,堆栈是可以释放/再利用的,也就解决递归过深而引起的溢出问题,这也是尾递归的优势所在。 [b]编译器优化[/b] 尾递归优化,看起来是蛮美好的,但在net中却有点乱糟糟的感觉。 1.Net在C#语言中是JIT编译成汇编时进行优化的。 2.Net在IL上,有个特殊指令tail去实现尾递归优化的(F#中)。 我们执行 TailRecursion(0)(x==1000000) 得出如下结论: C#/64位/Release是有JIT编译器进行尾递归优化的(非C#编译器优化)。 [img]http://files.jb51.net/file_images/article/201504/201541085234815.png?201531085241[/img] C#/32位或C#/Debug模式中JIT是不进行优化的。 [img]http://files.jb51.net/file_images/article/201504/201541085401164.png?201531085415[/img] [b]F#在优化尾递归也分2种情况:[/b] 1、 简单的尾递归优化成while循环,如下:
[u]复制代码[/u] 代码如下:
let rec TailRecursion(x) =   if (x = 1000) then true   else TailRecursion(x + 1)
(方便演示C#呈现)编译器优化成:
[u]复制代码[/u] 代码如下:
public static bool TailRecursion(int x)     {         while (x != 0x3e8)         {             x++;         }         return true;     }
2、 复杂的尾递归,F#编译器会生成IL指令Tail进行优化,如下:
[u]复制代码[/u] 代码如下:
let TailRecursion2 a cont = cont (a + 1) 
优化成: [img]http://files.jb51.net/file_images/article/201504/201541085248688.png?201531085256[/img] 如何定义复杂的尾递归呢?通常是后继传递模式(CPS)。 F#中在debug模式下,需要在编译时配置: [img]http://files.jb51.net/file_images/article/201504/201541085304803.png?201531085310[/img] [b]总结[/b] 在C#语言(过程式/面向对象编程思想)中,优先考虑的是循环,而不是递归/尾递归。 但在函数式编程思想当中,递归/尾递归使用则是主流用法,就像在C#使用循环一样。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部