亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

什么是尾部呼叫優化?

什么是尾部呼叫優化?

什么是尾部呼叫優化?很簡單,什么是尾部優化?更具體地說,有人能給我們展示一些小的代碼片段,在哪里可以應用,哪些地方不能,并解釋原因?
查看完整描述

3 回答

?
倚天杖

TA貢獻1828條經驗 獲得超3個贊

尾部調用優化可以避免為函數分配新的堆??蚣埽驗檎{用函數只會返回從被調用函數獲得的值。最常見的用法是尾遞歸,其中為利用尾調用優化而編寫的遞歸函數可以使用常量堆??臻g。


方案是少數幾種在規范中保證任何實現都必須提供這種優化的編程語言之一。(JavaScript也是這樣,從ES6開始),下面是方案中階乘函數的兩個示例:


(define (fact x)

  (if (= x 0) 1

      (* x (fact (- x 1)))))


(define (fact x)

  (define (fact-tail x accum)

    (if (= x 0) accum

        (fact-tail (- x 1) (* x accum))))

  (fact-tail x 1))

第一個函數不是尾遞歸函數,因為在進行遞歸調用時,函數需要跟蹤調用返回后與結果有關的乘法。因此,堆??雌饋砣缦拢?/p>


(fact 3)

(* 3 (fact 2))

(* 3 (* 2 (fact 1)))

(* 3 (* 2 (* 1 (fact 0))))

(* 3 (* 2 (* 1 1)))

(* 3 (* 2 1))

(* 3 2)

6

相反,尾遞歸階乘的堆棧跟蹤如下所示:


(fact 3)

(fact-tail 3 1)

(fact-tail 2 3)

(fact-tail 1 6)

(fact-tail 0 6)

6

正如您所看到的,我們只需要跟蹤對事實尾的每一次調用的相同數據量,因為我們只是簡單地將得到的值直接返回到頂部。這意味著,即使我打電話給(事實1000000),我只需要與(事實3)相同的空間。非尾遞歸事實并非如此,因此較大的值可能會導致堆棧溢出。


查看完整回答
反對 回復 2019-06-04
?
動漫人物

TA貢獻1815條經驗 獲得超10個贊

讓我們來看看一個簡單的例子:用C實現的階乘函數。

我們從明顯的遞歸定義開始。

unsigned fac(unsigned n){
    if (n < 2) return 1;
    return n * fac(n - 1);}

如果函數返回之前的最后一次操作是另一個函數調用,則函數以尾調用結束。如果這個調用相同的函數,那么它是尾遞歸的.

即使fac()乍一看,它看起來像是尾遞歸,而不是實際發生的事情。

unsigned fac(unsigned n){
    if (n < 2) return 1;
    unsigned acc = fac(n - 1);
    return n * acc;}

最后一個操作是乘法,而不是函數調用。

然而,重寫是可能的fac()要成為尾遞歸,可以將累積值作為附加參數傳遞到調用鏈中,并將最終結果作為返回值再次傳遞:

unsigned fac(unsigned n){
    return fac_tailrec(1, n);}unsigned fac_tailrec(unsigned acc, unsigned n){
    if (n < 2) return acc;
    return fac_tailrec(n * acc, n - 1);}

現在,為什么這是有用的?因為我們在尾調用之后立即返回,所以我們可以在以尾位置調用函數之前丟棄以前的堆棧幀,或者,在遞歸函數的情況下,可以按原樣重用堆棧幀。

尾叫優化將我們的遞歸代碼轉換為

unsigned fac_tailrec(unsigned acc, unsigned n){TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;}

這可以內聯成fac()我們到達

unsigned fac(unsigned n){
    unsigned acc = 1;TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;}

相當于

unsigned fac(unsigned n){
    unsigned acc = 1;

    for (; n > 1; --n)
        acc *= n;

    return acc;}

正如我們在這里看到的,一個足夠高級的優化器可以用迭代代替尾遞歸,這是更有效的,因為您避免了函數調用開銷,并且只使用了固定的堆??臻g。


查看完整回答
反對 回復 2019-06-04
?
互換的青春

TA貢獻1797條經驗 獲得超6個贊

TCO(尾調用優化)是智能編譯器調用函數而不占用額外堆??臻g的過程。這個只有當在函數中執行最后一條指令時才會發生這種情況。f是對函數g的調用。(注:g可以f)。這里的關鍵是f不再需要堆??臻g-它只是調用g然后再返回任何東西g會回來的。在這種情況下,優化可以使g只運行并返回調用f的東西的任何值。

這種優化可以使遞歸調用占用恒定的堆??臻g,而不是爆炸。

示例:這個階乘函數不是TCOptimable的:

def fact(n):
    if n == 0:
        return 1
    return n * fact(n-1)

該函數除了在其返回語句中調用另一個函數外,還執行其他任務。

下面的函數是TCOptimable的:

def fact_h(n, acc):
    if n == 0:
        return acc    return fact_h(n-1, acc*n)def fact(n):
    return fact_h(n, 1)

這是因為這些函數中的最后一件事是調用另一個函數。


查看完整回答
反對 回復 2019-06-04
  • 3 回答
  • 0 關注
  • 857 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號