歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux教程

Linux系統:閉包的概念形式與應用(5)

隨著硬件性能的提升以及編譯技術和虛擬機技術的改進,一些曾被性能問題所限制的動態語言開始受到關注,Python、Ruby 和 Lua 等語言都開始在應用中嶄露頭角。動態語言因其方便快捷的開發方式成為很多人喜愛的編程語言,伴隨動態語言的流行,我們經常聽到一個名詞——閉包,很多人會問閉包是什麼?閉包是用來做什麼的?本文匯集了有關閉包的概念、應用及其在一些編程語言中的表現形式,以供參考。
    什麼是閉包? 
    閉包並不是什麼新奇的概念,它早在高級語言開始發展的年代就產生了。閉包(Closure)是詞法閉包(Lexical Closure)的簡稱。對閉包的具體定義有很多種說法,這些說法大體可以分為兩類: 
    一種說法認為閉包是符合一定條件的函數,比如參考資源中這樣定義閉包:閉包是在其詞法上下文中引用了自由變量(注 1)的函數。
    另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。比如參考資源中就有這樣的的定義:在實現深約束(注 2)時,需要創建一個能顯式表示引用環境的東西,並將它與相關的子程序捆綁在一起,這樣捆綁起來的整體被稱為閉包。
    這兩種定義在某種意義上是對立的,一個認為閉包是函數,另一個認為閉包是函數和引用環境組成的整體。雖然有些咬文嚼字,但可以肯定第二種說法更確切。閉包只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就確定了,不會在執行時發生變化,所以一個函數只有一個實例。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。所謂引用環境是指在程序執行中的某個點所有處於活躍狀態的約束所組成的集合。其中的約束是指一個變量的名字和其所代表的對象之間的聯系。那麼為什麼要把引用環境與函數組合起來呢?這主要是因為在支持嵌套作用域的語言中,有時不能簡單直接地確定函數的引用環境。這樣的語言一般具有這樣的特性: 
    函數是一階值(First-class value),即函數可以作為另一個函數的返回值或參數,還可以作為一個變量的值。
    函數可以嵌套定義,即在一個函數內部可以定義另一個函數。
    這些概念上的解釋很難理解,顯然一個實際的例子更能說明問題。Lua 語言的語法比較接近偽代碼,我們來看一段 Lua 的代碼: 
    清單 1. 閉包示例1 
    function make_counter()
     local count = 0 
     function inc_count()
     count = count + 1
            return count 
    end
    return inc_countendc1 = make_counter()c2 = make_counter()print(c1())print(c2()) 
    在這段程序中,函數 inc_count 定義在函數 make_counter 內部,並作為 make_counter 的返回值。變量 count 不是 inc_count 內的局部變量,按照最內嵌套作用域的規則,inc_count 中的 count 引用的是外層函數中的局部變量 count。接下來的代碼中兩次調用 make_counter() ,並把返回值分別賦值給 c1 和 c2 ,然後又依次打印調用 c1 和 c2 所得到的返回值。 
    這裡存在一個問題,當調用 make_counter 時,在其執行上下文中生成了局部變量 count 的實例,所以函數 inc_count 中的 count 引用的就是這個實例。但是 inc_count 並沒有在此時被執行,而是作為返回值返回。當 make_counter 返回後,其執行上下文將失效,count 實例的生命周期也就結束了,在後面對 c1 和 c2 調用實際是對 inc_count 的調用,而此處並不在 count 的作用域中,這看起來是無法正確執行的。 
    上面的例子說明了把函數作為返回值時需要面對的問題。當把函數作為參數時,也存在相似的問題。下面的例子演示了把函數作為參數的情況。 
    清單 2. 閉包示例2 
    function do10times(n)
     for i = 0,9 do
     fn(i)
     end
    end 
    sum = 0
    function addsum(i)
     sum = sum + i
    end 
    do10times(addsum)
    print(sum) 
    這裡我們看到,函數 addsum 被傳遞給函數 do10times,被並在 do10times 中被調用10次。不難看出 addsum 實際的執行點在 do10times 內部,它要訪問非局部變量 sum,而 do10times 並不在 sum 的作用域內。這看起來也是無法正常執行的。 
    這兩種情況所面臨的問題實質是相同的。在這樣的語言中,如果按照作用域規則在執行時確定一個函數的引用環境,那麼這個引用環境可能和函數定義時不同。要想使這兩段程序正常執行,一個簡單的辦法是在函數定義時捕獲當時的引用環境,並與函數代碼組合成一個整體。當把這個整體當作函數調用時,先把其中的引用環境覆蓋到當前的引用環境上,然後執行具體代碼,並在調用結束後恢復原來的引用環境。這樣就保證了函數定義和執行時的引用環境是相同的。這種由引用環境與函數代碼組成的實體就是閉包。當然如果編譯器或解釋器能夠確定一個函數在定義和運行時的引用環境是相同的(注 3),那就沒有必要把引用環境和代碼組合起來了,這時只需要傳遞普通的函數就可以了。現在可以得出這樣的結論:閉包不是函數,只是行為和函數相似,不是所有被傳遞的函數都需要轉化為閉包,只有引用環境可能發生變化的函數才需要這樣做。 
    再次觀察上面兩個例子會發現,代碼中並沒有通過名字來調用函數 inc_count 和 addsum,所以他們根本不需要名字。
Copyright © Linux教程網 All Rights Reserved