中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Go中閉包的底層原理是什么

發布時間:2021-10-27 15:28:17 來源:億速云 閱讀:129 作者:小新 欄目:開發技術

這篇文章將為大家詳細講解有關Go中閉包的底層原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

1. 什么是閉包?

一個函數內引用了外部的局部變量,這種現象,就稱之為閉包。

例如下面的這段代碼中,adder 函數返回了一個匿名函數,而該匿名函數中引用了 adder 函數中的局部變量 sum ,那這個函數就是一個閉包。

package main 
 
import "fmt" 
 
func adder() func(int) int { 
    sum := 0 
    return func(x int) int { 
        sum += x 
        return sum 
    } 
}

而這個閉包中引用的外部局部變量并不會隨著 adder 函數的返回而被從棧上銷毀。

我們嘗試著調用這個函數,發現每一次調用,sum 的值都會保留在 閉包函數中以待使用。

func main() { 
     valueFunc:= adder() 
     fmt.Println(valueFunc(2))     // output: 2 
     fmt.Println(valueFunc(2))   // output: 4 
}

2. 復雜的閉包場景

寫一個閉包是比較容易的事,但單單會寫簡單的閉包函數,還遠遠不夠,如果不搞清楚閉包真正的原理,那很容易在一些復雜的閉包場景中對函數的執行邏輯進行誤判。

別的不說,就拿下來這個例子來說吧?

你覺得它會打印什么呢?

是 6 還是 11 呢?

import "fmt" 
 
func func1() (i int) { 
    i = 10 
    defer func() { 
        i += 1 
    }() 
    return 5 
} 
 
func main() { 
    closure := func1() 
    fmt.Println(closure) 
}

3. 閉包的底層原理?

還是以最上面的例子來分析

package main 
 
import "fmt" 
 
func adder() func(int) int { 
    sum := 0 
    return func(x int) int { 
        sum += x 
        return sum 
    } 
} 
 
func main() { 
    valueFunc:= adder() 
    fmt.Println(valueFunc(2))     // output: 2 
}

我們先對它進行逃逸分析,很容易發現 sum 作為 adder 函數局部變量,并不是分配在棧上,而是分配在堆上的。

這就解決了第一個疑惑:為什么 adder 函數返回后, sum 不會隨之銷毀?

$ go build -gcflags="-m -m -l" demo.go 
# command-line-arguments 
./demo.go:8:3: adder.func1 capturing by ref: sum (addr=true assign=true width=8) 
./demo.go:7:9: func literal escapes to heap: 
./demo.go:7:9:   flow: ~r0 = &{storage for func literal}: 
./demo.go:7:9:     from func literal (spill) at ./demo.go:7:9 
./demo.go:7:9:     from return func literal (return) at ./demo.go:7:2 
./demo.go:6:2: sum escapes to heap: 
./demo.go:6:2:   flow: {storage for func literal} = &sum: 
./demo.go:6:2:     from func literal (captured by a closure) at ./demo.go:7:9 
./demo.go:6:2:     from sum (reference) at ./demo.go:8:3 
./demo.go:6:2: moved to heap: sum 
./demo.go:7:9: func literal escapes to heap 
./demo.go:15:23: valueFunc(2) escapes to heap: 
./demo.go:15:23:   flow: {storage for ... argument} = &{storage for valueFunc(2)}: 
./demo.go:15:23:     from valueFunc(2) (spill) at ./demo.go:15:23 
./demo.go:15:23:   flow: {heap} = {storage for ... argument}: 
./demo.go:15:23:     from ... argument (spill) at ./demo.go:15:13 
./demo.go:15:23:     from fmt.Println(valueFunc(2)) (call parameter) at ./demo.go:15:13 
./demo.go:15:13: ... argument does not escape 
./demo.go:15:23: valueFunc(2) escapes to heap

可另一個問題,又浮現出來了,就算它不會銷毀,那閉包函數若是存儲的若是 sum 拷貝后的值,那每次調用閉包函數,里面的 sum 應該都是一樣的,調用兩次都應該返回 2,而不是可以累加記錄。

因此,可以大膽猜測,閉包函數的結構體里存儲的是 sum 的指針。

為了驗證這一猜想,只能上匯編了。

通過執行下面的命令,可以輸出對應的匯編代碼

go build -gcflags="-S" demo.go

輸出的內容相當之多,我提取出下面最關鍵的一行代碼,它定義了閉包函數的結構體。

其中 F 是函數的指針,但這不是重點,重點是 sum 存儲的確實是指針,驗證了我們的猜。

type.noalg.struct { F uintptr; "".sum *int }(SB), CX

4. 迷題揭曉

有了上面第三節的背景知識,那對于第二節給出的這道題,想必你也有答案了。

首先,由于 i 在函數定義的返回值上聲明,因此根據 go 的 caller-save 模式, i 變量會存儲在 main 函數的棧空間。

然后,func1 return 重新把 5 賦值給了 i ,此時 i = 5

由于閉包函數存儲了這個變量 i 的指針。

因此最后,在 defer 中對 i 進行自增,是直接更新到 i 的指針上,此時 i = 5+1,所以最終打印出來的結果是 6

import "fmt" 
 
func func1() (i int) { 
    i = 10 
    defer func() { 
        i += 1 
    }() 
    return 5 
} 
 
func main() { 
    closure := func1() 
    fmt.Println(closure) 
}

5. 再度變題

上面那題聽懂了的話,再來看看下面這道題。

func1 的返回值我們不寫變量名 i 了,然后原先返回具體字面量,現在改成變量 i ,就是這兩小小小的改動,會導致運行結果大大不同,你可以思考一下結果。

import "fmt" 
 
func func1() (int) { 
    i := 10 
    defer func() { 
        i += 1 
    }() 
    return i 
} 
 
func main() { 
    closure := func1() 
    fmt.Println(closure) 
}

如果你在返回值里寫了變量名,那么該變量會存儲 main 的棧空間里,而如果你不寫,那 i 只能存儲在 func1 的棧空間里,與此同時,return 的值,不會作用于原變量 i 上,而是會存儲在該函數在另一塊棧內存里。

因此你在 defer 中對原 i 進行自增,并不會作用到 func1 的返回值上。

所以打印的結果,只能是 10。

你答對了嗎?

關于“Go中閉包的底層原理是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

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

go
AI

绵竹市| 龙山县| 桓仁| 五华县| 阿合奇县| 江城| 江门市| 河南省| 宣化县| 沁水县| 紫阳县| 赣榆县| 墨竹工卡县| 濉溪县| 海晏县| 建昌县| 通许县| 望奎县| 宣城市| 利津县| 永康市| 四子王旗| 龙南县| 兴城市| 西乌珠穆沁旗| 林州市| 库伦旗| 扶风县| 讷河市| 朝阳区| 连州市| 清河县| 绵竹市| 临漳县| 衡东县| 张北县| 鄢陵县| 阆中市| 元阳县| 涿鹿县| 盘山县|