溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

go語言閉包的知識點整理

發(fā)布時間:2021-08-27 08:59:12 來源:億速云 閱讀:169 作者:chen 欄目:編程語言

這篇文章主要講解了“go語言閉包的知識點整理”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“go語言閉包的知識點整理”吧!

go閉包

Golang 中,閉包是一個可以引用其作用域之外變量的函數(shù)。
換句話說,閉包是一個內部函數(shù),它可以訪問創(chuàng)建它的范圍內的變量。即使外部函數(shù)完成執(zhí)行并且作用域被破壞,依然可以訪問。
在深入研究閉包之前,需要了解什么是匿名函數(shù)。

匿名函數(shù)

顧名思義,匿名函數(shù)就是沒有名字的函數(shù)。
舉個例子,我們創(chuàng)建一個一般函數(shù)和匿名函數(shù)

package mainimport (
    "fmt")func sayhi1() { // 一般函數(shù)
    fmt.Println("hello golang, I am Regular function")}func main() {
    sayhi1()
    sayhi2 := func() { //匿名函數(shù)
        fmt.Println("hello golang, I am Anonymous function")
    }
    sayhi2()}

結果輸出:

hello golang, I am Regular functionhello golang, I am Anonymous function

通過創(chuàng)建一個返回函數(shù)的函數(shù)來使用匿名函數(shù)。

package mainimport (
    "fmt")func sayhello(s string) func() {
    return func() {
        fmt.Println("hello", s)
    }}func main() {
    sayhello := sayhello("golang")
    sayhello()}

結果輸出:

hello golang

常規(guī)函數(shù)和匿名函數(shù)唯一區(qū)別是匿名函數(shù)不是在包級別聲明的,它們被動態(tài)地聲明,通常要么被使用,要么被遺忘,要么被分配給一個變量供以后使用。

閉包的本質

閉包是包含自由變量的代碼塊,這些變量不在這個代碼塊內或者任何全局上下文中定義,而是在定義代碼塊的環(huán)境中定義。由于自由變量包含在代碼塊中,所以只要閉包還被使用,那么這些自由變量以及它們引用的對象就不會被釋放,要執(zhí)行的代碼為自由變量提供綁定的計算環(huán)境。
閉包的價值在于可以作為函數(shù)對象或者匿名函數(shù),對于類型系統(tǒng)而言,這意味著不僅要表示數(shù)據(jù)還要表示代碼。支持閉包的多數(shù)語言都將函數(shù)作為第一級對象,就是說這些函數(shù)可以存儲到變量中作為參數(shù)傳遞給其他函數(shù),最重要的是能夠被函數(shù)動態(tài)創(chuàng)建和返回。

Golang中的閉包同樣也會引用到函數(shù)外的變量,閉包的實現(xiàn)確保只要閉包還被使用,那么被閉包引用的變量會一直存在。從形式上看,匿名函數(shù)都是閉包。
我們來看個閉包例子:

package mainimport (
    "fmt")func caller() func() int {
    callerd := 1
    sum := 0
    return func() int {
        sum += callerd        return sum    }}func main() {
    next := caller()
    fmt.Println(next())
    fmt.Println(next())
    fmt.Println(next())}

結果輸出:

1
2
3

該例子中called 和sum 是自由變量,caller函數(shù)返回的匿名函數(shù)為自由變量提供了計算環(huán)境,匿名函數(shù)和自由變量組成的代碼塊其實就是閉包。在閉包函數(shù)中,只有匿名函數(shù)才能訪問自由變量called和sum,而無法通過其他途徑訪問,因此閉包自由變量的安全性。
按照命令式語言的規(guī)則,caller函數(shù)只是返回了匿名函數(shù)的地址,但在執(zhí)行匿名函數(shù)時將會由于在其作用域內找不到sum和called變量而出錯。而在函數(shù)式語言中,當內嵌函數(shù)體內引用到體外的變量時,將會把定義時涉及到的引用環(huán)境和函數(shù)體打包成一個整體(閉包)返回。閉包的使用和正常的函數(shù)調用沒有區(qū)別。

現(xiàn)在我們給出引用環(huán)境的定義:在程序執(zhí)行中的某個點所有處于活躍狀態(tài)的約束所組成的集合,其中的約束指的是一個變量的名字和其所代表的對象之間的聯(lián)系。
所以我們說:閉包=函數(shù)+引用環(huán)境

其實我們可以將閉包函數(shù)看成一個類(C++),一個閉包函數(shù)調用就是實例化一個對象,閉包的自由變量就是類的成員變量,閉包函數(shù)的參數(shù)就是類的函數(shù)對象的參數(shù)。在該例子中,next可以看作是實例化的一個對象,next()可以看做是對象函數(shù)調用的返回值。
這讓我們想起了一句名言:對象是附有行為的數(shù)據(jù),而閉包是附有數(shù)據(jù)的行為

閉包使用的一些例子

  • 利用閉包實現(xiàn)數(shù)據(jù)隔離
    假設你想創(chuàng)建一個函數(shù),該函數(shù)可以訪問即使在函數(shù)退出后仍然存在的數(shù)據(jù)。舉個例子,如果你想統(tǒng)計函數(shù)被調用的次數(shù),但不希望其他任何人訪問該數(shù)據(jù)(這樣他們就不會意外更改它),你就可以用閉包來實現(xiàn)它:

package mainimport (
    "fmt")func caller() func() int {
    callerd := 0
    return func() int {
        callerd++
        return callerd    }}func main() {
    next := caller()
    fmt.Println(next())
    fmt.Println(next())
    fmt.Println(next())}

結果輸出:

1
2
3
  • 利用閉包包裝函數(shù)和創(chuàng)建中間件
    Go 中的函數(shù)是一等公民。這意味著您不僅可以動態(tài)創(chuàng)建匿名函數(shù),還可以將函數(shù)作為參數(shù)傳遞給函數(shù)。例如,在創(chuàng)建 Web 服務器時,通常會提供一個功能來處理Web 請求到特定的路由。

package mainimport (
  "fmt"
  "net/http")func main() {
  http.HandleFunc("/hello", hello)
  http.ListenAndServe(":3000", nil)}func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h2>Hello!</h2>")}

在上面例子中,函數(shù) hello() 被傳遞給 http.HandleFunc() 函數(shù),并在該路由匹配時調用。
雖然這段代碼不需要閉包,但如果我們想用更多邏輯包裝我們的處理程序,閉包是非常有用的。一個完美的例子是我們可以通過創(chuàng)建中間件來在我們處理程序執(zhí)行之前或之后做一些其它的工作。
什么是中間件?
中間件基本上是可重用功能的一個奇特術語,它可以在設計用于處理 Web 請求的代碼之前和之后運行代碼。在 Go 中,這些通常是通過閉包來實現(xiàn)的,但在不同的編程語言中,可以通過其他方式來實現(xiàn)。
在編寫 Web 應用程序時使用中間件很常見,而且它們不僅可用于計時器(您將在下面看到一個示例)。例如,中間件可用于編寫代碼驗證用戶是否登錄過一次,然后將其應用到你的所有會員專頁。
讓我們看看一個簡單的計時器中間件在 Go 中是如何工作的。

package mainimport (
  "fmt"
  "net/http"
  "time")func main() {
  http.HandleFunc("/hello", timed(hello))
  http.ListenAndServe(":3000", nil)}func timed(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    f(w, r)
    end := time.Now()
    fmt.Println("The request took", end.Sub(start))
  }}func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h2>Hello!</h2>")}

timed() 函數(shù)接受一個可以用作處理函數(shù)的函數(shù),并返回一個相同類型的函數(shù),但返回的函數(shù)與傳遞它的函數(shù)不同。返回的閉包記錄當前時間,調用原始函數(shù),最后記錄結束時間并打印出請求的持續(xù)時間。同時對我們的處理程序函數(shù)內部實際發(fā)生的事情是不可知的。
現(xiàn)在我們要做的就是將我們的處理程序包裝在 timed(handler) 中并將閉包傳遞給 http.HandleFunc() 函數(shù)調用。

  • 利用閉包使用 sort 二分搜索
    使用標準庫中的包也經常需要閉包,例如 sort 包。這個包為我們提供了大量有用的功能和代碼,用于排序和搜索排序列表。例如,如果您想對一個整數(shù)切片進行排序,然后在切片中搜索數(shù)字 7,您可以像這樣使用 sort 包。

package mainimport (
  "fmt"
  "sort")func main() {
  numbers := []int{1, 11, -5, 7, 2, 0, 12}
  sort.Ints(numbers)
  fmt.Println("Sorted:", numbers)
  index := sort.SearchInts(numbers, 7)
  fmt.Println("7 is at index:", index)}

結果輸出:

Sorted: [-5 0 1 2 7 11 12]7 is at index: 4

如果要搜索的每個元素都是自定義類型的切片會發(fā)生什么?或者,如果您想找到第一個等于或大于 7 的數(shù)字的索引,而不僅僅是 7 的第一個索引?
為此,您可以使用 sort.Search() 函數(shù),并且您需要傳入一個閉包,該閉包可用于確定特定索引處的數(shù)字是否符合您的條件。
sort.Search() is a binary search
sort.Search 函數(shù)執(zhí)行二分搜索,因此它需要一個閉包,該閉包在滿足您的條件之前對任何索引返回 false,在滿足后返回 true。
讓我們使用上面描述的示例來看看它的實際效果;我們將搜索列表中第一個大于或等于 7 的數(shù)字的索引。

package mainimport (
    "fmt"
    "sort")func main() {
    numbers := []int{1, 11, -5, 8, 2, 0, 12}
    sort.Ints(numbers)
    fmt.Println("Sorted:", numbers)

    index := sort.Search(len(numbers), func(i int) bool {
        return numbers[i] >= 7
    })
    fmt.Println("The first number >= 7 is at index:", index)
    fmt.Println("The first number >= 7 is:", numbers[index])}

結果輸出:

Sorted: [-5 0 1 2 8 11 12]The first number >= 7 is at index: 4
The first number >= 7 is: 8

在這個例子中,我們的閉包是作為第二個參數(shù)傳遞給 sort.Search() 的簡單函數(shù)。
這個閉包訪問數(shù)字切片,即使它從未被傳入,并為任何大于或等于 7 的數(shù)字返回 true。通過這樣做,它允許 sort.Search() 工作而無需了解什么您使用的基礎數(shù)據(jù)類型是什么,或者您試圖滿足什么條件。它只需要知道特定索引處的值是否符合您的標準。

  • 用閉包+defer進行處理異常

package mainimport (
    "fmt")func handle() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println("some except had happend:", err)
        }
    }()
    var a *int = nil
    *a = 100}func main() {
    handle()}

結果輸出:

some except had happend: runtime error: invalid memory address or nil pointer dereference

recover函數(shù)用于終止錯誤處理流程。一般情況下,recover應該在一個使用defer關鍵字的函數(shù)中執(zhí)行以有效截取錯誤處理流程。如果沒有在發(fā)生異常的goroutine中明確調用恢復過程(調用recover函數(shù)),會導致該goroutine所屬的進程打印異常信息后直接退出
對于第三方庫的調用,在不清楚是否有panic的情況下,最好在適配層統(tǒng)一加上recover過程,否則會導致當前進程的異常退出,而這并不是我們所期望的。    

感謝各位的閱讀,以上就是“go語言閉包的知識點整理”的內容了,經過本文的學習后,相信大家對go語言閉包的知識點整理這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI