首頁 後端開發 Golang 分享優質筆記:go閉包的使用學習

分享優質筆記:go閉包的使用學習

Aug 26, 2021 pm 04:36 PM
golang go語言

下面由go語言教學專欄跟大家介紹關於go 閉包的學習使用,希望對需要的朋友有幫助!

go閉包

Golang 中,閉包是一個可以引用其作用域之外變數的函數。
換句話說,閉包是一個內部函數,它可以存取創建它的範圍內的變數。即使外部函數完成執行並且作用域被破壞,仍然可以存取。
在深入研究閉包之前,需要了解什麼是匿名函數。

匿名函數

顧名思義,匿名函數就是沒有名字的函數。
舉個例子,我們建立一個一般函數和匿名函數

package mainimport (
    "fmt")func sayhi1() { // 一般函数
    fmt.Println("hello golang, I am Regular function")}func main() {
    sayhi1()
    sayhi2 := func() { //匿名函数
        fmt.Println("hello golang, I am Anonymous function")
    }
    sayhi2()}
登入後複製

結果輸出:

hello golang, I am Regular functionhello golang, I am Anonymous function
登入後複製

透過建立一個傳回函數的函數來使用匿名函數。

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

結果輸出:

hello golang
登入後複製

常規函數和匿名函數唯一區別是匿名函數不是在包級別聲明的,它們被動態地聲明,通常要么被使用,要么被遺忘,要么被分配給一個變數供以後使用。

閉包的本質

閉包是包含自由變數的程式碼區塊,這些變數不在這個程式碼區塊內或任何全域上下文中定義,而是在定義程式碼區塊的環境中定義。由於自由變數包含在程式碼區塊中,所以只要閉包還被使用,那麼這些自由變數以及它們所引用的物件就不會被釋放,要執行的程式碼為自由變數提供綁定的計算環境。
閉包的價值在於可以作為函數物件或匿名函數,對於類型系統而言,這意味著不僅要表示資料還要表示程式碼。支援閉包的多數語言都將函數作為第一級對象,就是說這些函數可以儲存到變數中作為參數傳遞給其他函數,最重要的是能夠被函數動態建立和返回。

Golang中的閉包同樣也會引用到函數外的變量,閉包的實作確保只要閉包還被使用,那麼被閉包引用的變數就會一直存在。從形式上看,匿名函數都是閉包。
我們來看個閉包範例:

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函數傳回的匿名函數為自由變數提供了計算環境,匿名函數和自由變數組成的程式碼區塊其實就是閉包。在閉包函數中,只有匿名函數才能存取自由變數called和sum,而無法透過其他途徑訪問,因此閉包自由變數的安全性。
依照命令式語言的規則,caller函數只是傳回了匿名函式的位址,但執行匿名函式時將會因為在其作用域內找不到sum和called變數而出錯。而在函數式語言中,當內嵌函數體內引用到體外的變數時,會將定義時涉及的引用環境和函數體打包成一個整體(閉包)返回。閉包的使用和正常的函數呼叫沒有差別。

現在我們給出引用環境的定義:在程式執行中的某個點所有處於活躍狀態的限制所組成的集合,其中的約束指的是一個變數的名字和其所代表的對象之間的聯繫。
所以我們說:閉包=函數引用環境

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

#閉包使用的一些例子

  • 利用閉包實現資料隔離
    假設你想建立一個函數,該函數可以存取即使在函數退出後仍然存在的資料。舉個例子,如果你想統計函數被呼叫的次數,但不希望其他任何人存取該資料(這樣他們就不會意外更改它),你就可以用閉包來實現它:
#
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
登入後複製
登入後複製
  • 利用闭包包装函数和创建中间件
    Go 中的函数是一等公民。这意味着您不仅可以动态创建匿名函数,还可以将函数作为参数传递给函数。例如,在创建 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, "<h1>Hello!</h1>")}
登入後複製

在上面例子中,函数 hello() 被传递给 http.HandleFunc() 函数,并在该路由匹配时调用。
虽然这段代码不需要闭包,但如果我们想用更多逻辑包装我们的处理程序,闭包是非常有用的。一个完美的例子是我们可以通过创建中间件来在我们处理程序执行之前或之后做一些其它的工作。
什么是中间件?
中间件基本上是可重用功能的一个奇特术语,它可以在设计用于处理 Web 请求的代码之前和之后运行代码。在 Go 中,这些通常是通过闭包来实现的,但在不同的编程语言中,可以通过其他方式来实现。
在编写 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, "<h1>Hello!</h1>")}
登入後複製

timed() 函数接受一个可以用作处理函数的函数,并返回一个相同类型的函数,但返回的函数与传递它的函数不同。返回的闭包记录当前时间,调用原始函数,最后记录结束时间并打印出请求的持续时间。同时对我们的处理程序函数内部实际发生的事情是不可知的。
现在我们要做的就是将我们的处理程序包装在 timed(handler) 中并将闭包传递给 http.HandleFunc() 函数调用。

  • 利用闭包使用 sort 二分搜索
    使用标准库中的包也经常需要闭包,例如 sort 包。这个包为我们提供了大量有用的功能和代码,用于排序和搜索排序列表。例如,如果您想对一个整数切片进行排序,然后在切片中搜索数字 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
登入後複製

如果要搜索的每个元素都是自定义类型的切片会发生什么?或者,如果您想找到第一个等于或大于 7 的数字的索引,而不仅仅是 7 的第一个索引?
为此,您可以使用 sort.Search() 函数,并且您需要传入一个闭包,该闭包可用于确定特定索引处的数字是否符合您的条件。
sort.Search() is a binary search
sort.Search 函数执行二分搜索,因此它需要一个闭包,该闭包在满足您的条件之前对任何索引返回 false,在满足后返回 true。
让我们使用上面描述的示例来看看它的实际效果;我们将搜索列表中第一个大于或等于 7 的数字的索引。

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
登入後複製

在这个例子中,我们的闭包是作为第二个参数传递给 sort.Search() 的简单函数。
这个闭包访问数字切片,即使它从未被传入,并为任何大于或等于 7 的数字返回 true。通过这样做,它允许 sort.Search() 工作而无需了解什么您使用的基础数据类型是什么,或者您试图满足什么条件。它只需要知道特定索引处的值是否符合您的标准。

  • 用闭包+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函数用于终止错误处理流程。一般情况下,recover应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(调用recover函数),会导致该goroutine所属的进程打印异常信息后直接退出
对于第三方库的调用,在不清楚是否有panic的情况下,最好在适配层统一加上recover过程,否则会导致当前进程的异常退出,而这并不是我们所期望的。

                                      

以上是分享優質筆記:go閉包的使用學習的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1665
14
CakePHP 教程
1424
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? 在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? Apr 02, 2025 pm 04:54 PM

Go語言中使用RedisStream實現消息隊列時類型轉換問題在使用Go語言與Redis...

GoLand中自定義結構體標籤不顯示怎麼辦? GoLand中自定義結構體標籤不顯示怎麼辦? Apr 02, 2025 pm 05:09 PM

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...

Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Apr 02, 2025 pm 04:12 PM

Go語言中哪些庫是大公司開發或知名開源項目?在使用Go語言進行編程時,開發者常常會遇到一些常見的需求,�...

Golang的目的:建立高效且可擴展的系統 Golang的目的:建立高效且可擴展的系統 Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? 在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? Apr 02, 2025 pm 05:03 PM

Go編程中的資源管理:Mysql和Redis的連接與釋放在學習Go編程過程中,如何正確管理資源,特別是與數據庫和緩存�...

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

多進程日誌寫入如何保證並發安全又高效? 多進程日誌寫入如何保證並發安全又高效? Apr 02, 2025 pm 03:51 PM

高效處理多進程日誌寫入的並發安全問題多進程同時寫入同一個日誌文件,如何保證並發安全且高效?這是一個...

使用Go語言連接Oracle數據庫時是否需要安裝Oracle客戶端? 使用Go語言連接Oracle數據庫時是否需要安裝Oracle客戶端? Apr 02, 2025 pm 03:48 PM

使用Go語言連接Oracle數據庫時是否需要安裝Oracle客戶端?在使用Go語言開發時,連接Oracle數據庫是一個常見需求�...

See all articles