<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 面試題 013:Go 中閉包的底層原理是?

          共 4168字,需瀏覽 9分鐘

           ·

          2021-10-26 18:49

          大家好,我是明哥。

          歡迎大家再次來(lái)到??Go 語(yǔ)言面試題庫(kù)?這個(gè)專欄

          本篇問(wèn)題:Go 中閉包的底層原理?

          # 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ì)寫簡(jiǎn)單的閉包函數(shù),還遠(yuǎn)遠(yuǎn)不夠,如果不搞清楚閉包真正的原理,那很容易在一些復(fù)雜的閉包場(chǎng)景中對(duì)函數(shù)的執(zhí)行邏輯進(jìn)行誤判。

          別的不說(shuō),就拿下來(lái)這個(gè)例子來(lái)說(shuō)吧?

          你覺(jué)得它會(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. 閉包的底層原理?

          還是以最上面的例子來(lái)分析

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

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

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

          通過(guò)執(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ù)的棧空間。

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

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

          因此最后,在 defer 中對(duì) i 進(jìn)行自增,是直接更新到 i 的指針上,此時(shí) i = 5+1,所以最終打印出來(lái)的結(jié)果是 6

          import?"fmt"

          func?func1()?(i?int)?{
          ????i?=?10
          ????defer?func()?{
          ????????i?+=?1
          ????}()
          ????return?5
          }

          func?main()?{
          ????closure?:=?func1()
          ????fmt.Println(closure)
          }

          # 5. 再度變題

          上面那題聽(tīng)懂了的話,再來(lái)看看下面這道題。

          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 的棧空間里,而如果你不寫,那 i 只能存儲(chǔ)在 func1 的棧空間里,與此同時(shí),return 的值,不會(huì)作用于原變量 i 上,而是會(huì)存儲(chǔ)在該函數(shù)在另一塊棧內(nèi)存里。

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

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

          你答對(duì)了嗎?

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

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

          這是為什么呢?

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

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

          是不是很簡(jiǎn)單呢?

          本系列的所有文章,我都開(kāi)放到 Github 上:https://github.com/iswbm/golang-interview

          這個(gè)號(hào)沒(méi)有留言功能呢?,如果文章有寫得不對(duì)的地方,可以去那里提交 issue 幫我指正。順便可以幫我點(diǎn)個(gè)小 ??,在那里我對(duì)題庫(kù)進(jìn)行了分類整理,方便索引查找。

          加油噢,我們下篇見(jiàn)!


          文章的最后,插播一個(gè)福利

          雙十一快到了,阿里云也開(kāi)始搞活動(dòng)了,剛好我這邊可以帶大家白Piao?阿里云的服務(wù)器。

          說(shuō)白了就是大家?可以一分錢不花,就可以領(lǐng)到服務(wù)器,規(guī)格是 2c2m(2vcpu 2G memory) 的機(jī)器。

          昨天在朋友圈發(fā)了下,已經(jīng)有 400 人報(bào)名參與了,今天這篇文章再說(shuō)一下,有想參加的朋友,可以加我下微信,備注『服務(wù)器,我統(tǒng)一拉群,帶大家一起薅羊毛。


          ? ?


          喜歡明哥文章的同學(xué)
          歡迎長(zhǎng)按下圖訂閱!

          ???

          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美精品狂野欧美成人 | 欧美日韩国产成人 | 欧美成人在线观看网站 | 亚洲无码高清在线观看 | 黄色尤物国产黄色小视频在线观看免费 |