欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go 中閉包的底層原理

 更新時(shí)間:2021年10月27日 14:28:34   作者:寫代碼的明哥  
這篇文章主要介紹了Go 中閉包的底層原理,閉包的基本原理是一種現(xiàn)象,一個(gè)函數(shù)內(nèi)引用了外部的局部變量的現(xiàn)象,帶著些許的了解和小編一起進(jìn)入文章正題學(xué)習(xí)

1. 什么是閉包?

一個(gè)函數(shù)內(nèi)引用了外部的局部變量,這種現(xiàn)象,就稱之為閉包。

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

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

而這個(gè)閉包中引用的外部局部變量并不會(huì)隨著 adder 函數(shù)的返回而被從棧上銷毀。

我們嘗試著調(diào)用這個(gè)函數(shù),發(fā)現(xiàn)每一次調(diào)用,sum 的值都會(huì)保留在 閉包函數(shù)中以待使用。

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

2. 復(fù)雜的閉包場(chǎng)景

寫一個(gè)閉包是比較容易的事,但單單會(huì)寫簡單的閉包函數(shù),還遠(yuǎn)遠(yuǎn)不夠,如果不搞清楚閉包真正的原理,那很容易在一些復(fù)雜的閉包場(chǎng)景中對(duì)函數(shù)的執(zhí)行邏輯進(jìn)行誤判。

別的不說,就拿下來這個(gè)例子來說吧?

你覺得它會(huì)打印什么呢?

是 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 
} 

我們先對(duì)它進(jìn)行逃逸分析,很容易發(fā)現(xiàn) sum 作為 adder 函數(shù)局部變量,并不是分配在棧上,而是分配在堆上的。

這就解決了第一個(gè)疑惑:為什么 adder 函數(shù)返回后, sum 不會(huì)隨之銷毀?

$ 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 

可另一個(gè)問題,又浮現(xiàn)出來了,就算它不會(huì)銷毀,那閉包函數(shù)若是存儲(chǔ)的若是 sum 拷貝后的值,那每次調(diào)用閉包函數(shù),里面的 sum 應(yīng)該都是一樣的,調(diào)用兩次都應(yīng)該返回 2,而不是可以累加記錄。

因此,可以大膽猜測(cè),閉包函數(shù)的結(jié)構(gòu)體里存儲(chǔ)的是 sum 的指針。

為了驗(yàn)證這一猜想,只能上匯編了。

通過執(zhí)行下面的命令,可以輸出對(duì)應(yīng)的匯編代碼

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

輸出的內(nèi)容相當(dāng)之多,我提取出下面最關(guān)鍵的一行代碼,它定義了閉包函數(shù)的結(jié)構(gòu)體。

其中 F 是函數(shù)的指針,但這不是重點(diǎn),重點(diǎn)是 sum 存儲(chǔ)的確實(shí)是指針,驗(yàn)證了我們的猜。

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

4. 迷題揭曉

有了上面第三節(jié)的背景知識(shí),那對(duì)于第二節(jié)給出的這道題,想必你也有答案了。

首先,由于 i 在函數(shù)定義的返回值上聲明,因此根據(jù) go 的 caller-save 模式, i 變量會(huì)存儲(chǔ)在 main 函數(shù)的??臻g。

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

由于閉包函數(shù)存儲(chǔ)了這個(gè)變量 i 的指針。

因此最后,在 defer 中對(duì) i 進(jìn)行自增,是直接更新到 i 的指針上,此時(shí) i = 5+1,所以最終打印出來的結(jié)果是 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 了,然后原先返回具體字面量,現(xiàn)在改成變量 i ,就是這兩小小小的改動(dòng),會(huì)導(dǎo)致運(yùn)行結(jié)果大大不同,你可以思考一下結(jié)果。

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

如果你在返回值里寫了變量名,那么該變量會(huì)存儲(chǔ) main 的??臻g里,而如果你不寫,那 i 只能存儲(chǔ)在 func1 的??臻g里,與此同時(shí),return 的值,不會(huì)作用于原變量 i 上,而是會(huì)存儲(chǔ)在該函數(shù)在另一塊棧內(nèi)存里。

因此你在 defer 中對(duì)原 i 進(jìn)行自增,并不會(huì)作用到 func1 的返回值上。

所以打印的結(jié)果,只能是 10。

你答對(duì)了嗎?

6. 最后一個(gè)問題

不知道你有沒有發(fā)現(xiàn),在第一節(jié)示例中的 sum 是存儲(chǔ)在堆內(nèi)存中的,而后面幾個(gè)示例都是存儲(chǔ)在棧內(nèi)存里。

這是為什么呢?

仔細(xì)對(duì)比,不難發(fā)現(xiàn),示例一返回的是閉包函數(shù),閉包函數(shù)在 adder 返回后還要在其他地方繼續(xù)使用,在這種情況下,為了保證閉包函數(shù)的正常運(yùn)行,無論閉包函數(shù)在哪里,i 都不能回收,所以 Go 編譯器會(huì)智能地將其分配在堆上。

而后面的其他示例,都只是涉及了閉包的特性,并不是直接把閉包函數(shù)返回,因此完全可以將其分配在棧上,非常的合理。

到此這篇關(guān)于Go 中閉包的底層原理的文章就介紹到這了,更多相關(guān)Go 閉包底層原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go處理json數(shù)據(jù)方法詳解(Marshal,UnMarshal)

    Go處理json數(shù)據(jù)方法詳解(Marshal,UnMarshal)

    這篇文章主要介紹了Go處理json數(shù)據(jù)的方法詳解,Marshal(),UnMarshal(),需要的朋友可以參考下
    2022-04-04
  • GPT回答 go語言和C語言數(shù)組操作對(duì)比

    GPT回答 go語言和C語言數(shù)組操作對(duì)比

    這篇文章主要為大家介紹了GPT回答的go語言和C語言數(shù)組操作方法對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Go語言異常處理(Panic和recovering)用法詳解

    Go語言異常處理(Panic和recovering)用法詳解

    異常處理是程序健壯性的關(guān)鍵,往往開發(fā)人員的開發(fā)經(jīng)驗(yàn)的多少從異常部分處理上就能得到體現(xiàn)。Go語言中沒有Try?Catch?Exception機(jī)制,但是提供了panic-and-recover機(jī)制,本文就來詳細(xì)講講他們的用法
    2022-07-07
  • golang中new與make的區(qū)別講解

    golang中new與make的區(qū)別講解

    new只能開辟單個(gè)空間,不能為引用類型開辟多個(gè)空間,并且new是對(duì)類型進(jìn)行內(nèi)存的開辟,返回一個(gè)指向該內(nèi)存空間的指針類型,如果使用new去初始化引用數(shù)據(jù)類型,不是很合適(當(dāng)然,new一個(gè)對(duì)象還是可以的),因此就需要用到另一個(gè)內(nèi)置函數(shù)make,需要的朋友可以參考下
    2023-01-01
  • golang跳轉(zhuǎn)語句goto,break,continue的使用及區(qū)別說明

    golang跳轉(zhuǎn)語句goto,break,continue的使用及區(qū)別說明

    這篇文章主要介紹了golang跳轉(zhuǎn)語句goto,break,continue的使用及區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang實(shí)現(xiàn)密碼加密的示例詳解

    Golang實(shí)現(xiàn)密碼加密的示例詳解

    數(shù)據(jù)庫在存儲(chǔ)密碼時(shí),不能明文存儲(chǔ),需要加密后存儲(chǔ),而Golang中的加密算法有很多種,下面小編就來通過簡單的示例和大家簡單聊聊吧
    2023-07-07
  • golang 中string和int類型相互轉(zhuǎn)換

    golang 中string和int類型相互轉(zhuǎn)換

    這篇文章主要介紹了golang 中string和int類型相互轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Go指針的具體使用

    Go指針的具體使用

    本文主要介紹了Go指針的具體使用,包括使用指針、空指針、指針數(shù)組、指向指針的指針等,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • 如何利用Go語言實(shí)現(xiàn)LRU?Cache

    如何利用Go語言實(shí)現(xiàn)LRU?Cache

    這篇文章主要介紹了如何利用Go語言實(shí)現(xiàn)LRU?Cache,LRU是Least?Recently?Used的縮寫,是一種操作系統(tǒng)中常用的頁面置換算法,下面我們一起進(jìn)入文章了解更多內(nèi)容吧,需要的朋友可以參考一下
    2022-03-03
  • 關(guān)于go-micro與其它gRPC框架之間的通信問題及解決方法

    關(guān)于go-micro與其它gRPC框架之間的通信問題及解決方法

    在之前的文章中分別介紹了使用gRPC官方插件和go-micro插件開發(fā)gRPC應(yīng)用程序的方式,都能正常走通。不過當(dāng)兩者混合使用的時(shí)候,互相訪問就成了問題,下面通過本文給大家講解下go-micro與gRPC框架通信問題,一起看看吧
    2022-04-04

最新評(píng)論