閉包的概念: 內層的函式可以引用包含在它外層的函式的變數,即使外層函式的執行已經終止。但該變數提供的值並非變數建立時的值,而是在父函式範圍內的最終值。
要理解閉包,首先要搞懂清楚是變數的作用域和生命週期。我們以C#為例:
在C#中,變數作用域有三種,一種是屬於類的,稱之為field;第二屬於函式的,我們通常稱之為區域性變數;還有一種,也是屬於函式的,不過它的作用範圍更小,它只屬於函式區域性的程式碼片段,這種同樣稱之為區域性變數。 這三種變數的生命週期基本都可以用一句話來說明,每個變數都屬於它所寄存的物件,即變數隨著其寄存物件生而生和消亡。對應三種作用域我們可以這樣說,類裡面的變數是隨著類的例項化而生,同時伴隨著類物件的資源回收而消亡(當然這裡不包括非例項化的static和const物件)。而函式(或程式碼片段)的變數也隨著函式(或程式碼片段)呼叫開始而生,伴隨函式(或程式碼片段)呼叫結束而自動由GC釋放,它內部變數生命週期滿足先進後出的特性。在作用域以外不能對變數進行讀寫等操作。
作用域外試圖去操作變數時,提示當前上下文不存在XXX等類似的錯誤提示。
那麼這裡有沒有例外呢? 答案是有的
先來看一段程式碼:
變數n實際上是屬於函式T1的區域性變數,它本來生命週期應該是伴隨著函式T1的呼叫結束而被釋放掉的,但這裡我們卻在返回的委託b中仍然能呼叫它,因為T1呼叫返回的匿名委託的程式碼片段中我們用到了n,而在編譯器看來,這些都是合法的,因為返回的委託b和函式T1存在上下文關係,也就是說匿名委託b是允許使用它所在的函式或者類裡面的區域性變數的,於是編譯器透過一系列動作(具體動作我們後面再說)使b中呼叫的函式T1的區域性變數自動閉合,從而使該區域性變數滿足新的作用範圍。
使用閉包,我們可以輕鬆的訪問外層函式定義的變數,這在匿名方法中普遍使用。比如有如下場景,在winform應用程式中,我們希望做這麼一個效果,當用戶關閉窗體時,給使用者一個提示框。我們將新增如下程式碼:如果我們不使用匿名函式,就必須用其他方式來把tipWords的值傳遞給FormClosing註冊的處理函式,這就增加了不必要的程式碼工作量。所以說閉包可以極大的簡化我們的程式碼工作量,使我們的程式碼更加優美簡潔。
應用閉包,我們要注意一個陷阱。比如有一個學生資訊的陣列,我們需要遍歷每一個使用者,對各個使用者做處理後輸出使用者名稱。 首先建立一個學生類,包含學生姓名和年齡
預想的輸出應該為:”張三”,”李四”,”王五”。
但是實際執行中會報錯:提示索引超出界限。
為什麼沒有達到我們預期的效果呢?讓我們再來看一下閉包的概念。內層函式引用的外層函式的變數時,該變數提供的值並非變數建立時的值,而是在父函式範圍內的最終值。就是說,當執行緒中執行方法時,方法中的i引數的值,並不是從0累加到2,而是始終是累加道德極限值,也就是3。原來如此,那我們應該如何避免這種陷阱呢?
C#中普遍的做法是,將匿名函式引用的變數用一個臨時變數儲存下來,然後在匿名函式中使用臨時變數。我們再執行來看,輸出依次為 ”張三”,”李四”,”王五”。.注意,每次的輸出順序可能不同,這是由於此處的執行緒執行順序是由CPU排程的。
最後,閉包並不是針對某一特定語言的概念,而是一個通用的概念。除了在各個支援函數語言程式設計的語言中,我們會接觸到它。一些不支援函數語言程式設計的語言中也能支援閉包(如java8之前的匿名內部類)。
閉包的概念: 內層的函式可以引用包含在它外層的函式的變數,即使外層函式的執行已經終止。但該變數提供的值並非變數建立時的值,而是在父函式範圍內的最終值。
要理解閉包,首先要搞懂清楚是變數的作用域和生命週期。我們以C#為例:
在C#中,變數作用域有三種,一種是屬於類的,稱之為field;第二屬於函式的,我們通常稱之為區域性變數;還有一種,也是屬於函式的,不過它的作用範圍更小,它只屬於函式區域性的程式碼片段,這種同樣稱之為區域性變數。 這三種變數的生命週期基本都可以用一句話來說明,每個變數都屬於它所寄存的物件,即變數隨著其寄存物件生而生和消亡。對應三種作用域我們可以這樣說,類裡面的變數是隨著類的例項化而生,同時伴隨著類物件的資源回收而消亡(當然這裡不包括非例項化的static和const物件)。而函式(或程式碼片段)的變數也隨著函式(或程式碼片段)呼叫開始而生,伴隨函式(或程式碼片段)呼叫結束而自動由GC釋放,它內部變數生命週期滿足先進後出的特性。在作用域以外不能對變數進行讀寫等操作。
作用域外試圖去操作變數時,提示當前上下文不存在XXX等類似的錯誤提示。
那麼這裡有沒有例外呢? 答案是有的
先來看一段程式碼:
變數n實際上是屬於函式T1的區域性變數,它本來生命週期應該是伴隨著函式T1的呼叫結束而被釋放掉的,但這裡我們卻在返回的委託b中仍然能呼叫它,因為T1呼叫返回的匿名委託的程式碼片段中我們用到了n,而在編譯器看來,這些都是合法的,因為返回的委託b和函式T1存在上下文關係,也就是說匿名委託b是允許使用它所在的函式或者類裡面的區域性變數的,於是編譯器透過一系列動作(具體動作我們後面再說)使b中呼叫的函式T1的區域性變數自動閉合,從而使該區域性變數滿足新的作用範圍。
閉包的優點:使用閉包,我們可以輕鬆的訪問外層函式定義的變數,這在匿名方法中普遍使用。比如有如下場景,在winform應用程式中,我們希望做這麼一個效果,當用戶關閉窗體時,給使用者一個提示框。我們將新增如下程式碼:如果我們不使用匿名函式,就必須用其他方式來把tipWords的值傳遞給FormClosing註冊的處理函式,這就增加了不必要的程式碼工作量。所以說閉包可以極大的簡化我們的程式碼工作量,使我們的程式碼更加優美簡潔。
閉包的陷阱:應用閉包,我們要注意一個陷阱。比如有一個學生資訊的陣列,我們需要遍歷每一個使用者,對各個使用者做處理後輸出使用者名稱。 首先建立一個學生類,包含學生姓名和年齡
預想的輸出應該為:”張三”,”李四”,”王五”。
但是實際執行中會報錯:提示索引超出界限。
為什麼沒有達到我們預期的效果呢?讓我們再來看一下閉包的概念。內層函式引用的外層函式的變數時,該變數提供的值並非變數建立時的值,而是在父函式範圍內的最終值。就是說,當執行緒中執行方法時,方法中的i引數的值,並不是從0累加到2,而是始終是累加道德極限值,也就是3。原來如此,那我們應該如何避免這種陷阱呢?
C#中普遍的做法是,將匿名函式引用的變數用一個臨時變數儲存下來,然後在匿名函式中使用臨時變數。我們再執行來看,輸出依次為 ”張三”,”李四”,”王五”。.注意,每次的輸出順序可能不同,這是由於此處的執行緒執行順序是由CPU排程的。
最後,閉包並不是針對某一特定語言的概念,而是一個通用的概念。除了在各個支援函數語言程式設計的語言中,我們會接觸到它。一些不支援函數語言程式設計的語言中也能支援閉包(如java8之前的匿名內部類)。