對于閉包的概念,起先是函數式程式設計的語言才有的,如LISP,Haskell等。通常是支援函數式程式設計的語言才有的特性。目前很多指令式語言諸如javascript,C#等也能實作函數式程式設計,也有了閉包的概念。
閉包的定義比較抽象晦澀,本人了解的閉包的概念其實就是在函數中定義一個子函數,通過該子函數可以讀取父函數中的值。下面以F#為例。F#是微軟發展的基于.NET的一個函數式語言。
假設有一個函數,求一個數平方和。
- //計算平方和
- let calculate x y=
- let square1=x*x
- let square2=y*y
- let squaresum()=
- square1+square2
- squaresum()
- printfn "%i" (calculate 3 4)
結果如下:
此處 squaresum 函數就是一個閉包函數,它可以讀取square1和square2的值。
事實上,該函數可以柯裡化,柯裡化後就隻有1個參數,傳回的一個squaresum函數。
- let calculate x=
- let squaresum=fun y->square1+y*y
- squaresum
此squaresum函數實際上也是閉包函數,它能讀取square1的值。注意此 calculate 函數的調用方式,他先用參數3運算,傳回的是一個函數squaresum,然後由squaresum再次調用參數4,計算出是25。是以,可以看出,在第二次調用的時候,不經意的已經使用了square1參數。如果還不夠了解,可以看下面的寫法,拆開來寫。
- let square1=x*x;
- printfn "call calculate->square1:%i" square1;
- let squaresum=fun y->(printfn "call squaresum->square1:%i" square1;square1+y*y)
- let tmp_squaresum=calculate 3;
- printfn "%i" (tmp_squaresum 4)
由代碼運作結果看到,先調用calculate,此函數算出square1的值為9,列印出。然後把squaresum作為傳回值後,指派給tmp_squaresum,然後退出了。接着,調用tmp_squaresum了。神奇的發現square1的值還是9。這就是閉包的作用。雖然主函數運作結束了,但是由于它的子函數需要用到主函數中的值,是以該函數仍然持有在記憶體中,并不馬上退出。
程式員不需要做什麼特殊的工作來保持這個局部變量,.NET的編譯器會自動的處理閉包,将父函數中的局部變量仍然持有着,直到垃圾回收機制來回收。由于局部變量不會被釋放,是以過度的使用閉包,導緻記憶體緊張,是以沒有必要的情況下,盡量不使用閉包。