詳解golang中的閉包與defer
閉包與defer
1.閉包
閉包 : 一個函數(shù)與其相關(guān)的引用環(huán)境組合的一個實體,其實可以理解為面向?qū)ο笾蓄愔械膶傩耘c方法。
如代碼塊中,函數(shù)function的返回值(匿名函數(shù))與變量n就是1個閉包。
該匿名函數(shù)就相當(dāng)于類中的方法 變量n相當(dāng)于類中的屬性
// 無形參 返回值是該匿名函數(shù)
func function() func(int) int {
var n int = 10 // 相當(dāng)于類屬性
return func(x int) int { //匿名函數(shù)
x = x + n
return x
}
}
var f func(int) int = function()
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 13再舉幾個例子:
//示例1
func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder2(10)
fmt.Println(f(10)) //20
fmt.Println(f(20)) //40
fmt.Println(f(30)) //70
f1 := adder2(20)
fmt.Println(f1(40)) //60
fmt.Println(f1(50)) //110
}
//示例2
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(txtFunc("test")) //test.txt
}2.defer
1.defer 是 Go 語言提供的一種用于注冊延遲調(diào)用的機制,每一次 defer 都會把函數(shù)壓入棧中,當(dāng)前函數(shù)返回前再把延遲函數(shù)取出并執(zhí)行。
defer 定義的函數(shù)會先進入一個棧,函數(shù) return 前,會按先進后出(FILO)的順序執(zhí)行。也就是說最先被定義的 defer 語句最后執(zhí)行。
2.defer 語句定義時,對 外部變量的引用 是有兩種方式的,分別是作為 函數(shù)參數(shù) 和作為 閉包引用。
- 作為 函數(shù)參數(shù),則在 defer 定義時 就把值傳遞給 defer,并被 緩存 起來;
- 作為 閉包引用 的話,則會在 defer 函數(shù)真正調(diào)用時根據(jù)整個上下文確定當(dāng)前的值。
下面就分別對這兩種情況舉例子。
情況一:
func trace(str string) string {
fmt.Println("entering " + str)
return str
}
func leave(str string) {
fmt.Println("leaving " + str)
}
func point() {
defer leave(trace("point"))
fmt.Println("in point")
}
func main() {
point()
}
//輸出結(jié)果:
//entering point
//in point
//leaving point
這是第一種情況,defer的函數(shù)接受的參數(shù)在它入棧的時候就被緩存下來了。
再舉個例子:
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
//10 1 2 3
//20 0 2 2
//2 0 2 2
//1 1 3 4
情況二:
要完全理解第二條規(guī)則,需要了解 return 與 defer 是怎么運行的。
函數(shù)內(nèi)的 return xxx 并不是一個原子執(zhí)行的返回:即不是先執(zhí)行 return xxx 再執(zhí)行 defer,也不是先執(zhí)行 defer 再執(zhí)行 return xxx。而是將 return xxx 拆分開來,經(jīng)過編譯后執(zhí)行過程如下:
1. 返回變量 = xxx 2. 調(diào)用 defer 函數(shù)(有可能更新返回變量的值) 3. return 返回變量。
1.
func f1() (r int) {
defer func() {
r++
}()
return 0
}
2.
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
3.
func f3() (r int) {
defer func(r int) { // 作為函數(shù)參數(shù)傳入 defer 函數(shù)
r = r + 5
}(r)
return 1
}
拆解:
1.r = 0 // 1. 賦值
func() { // 2. 運行 defer 函數(shù) r++,r = 1
r++
}()
return r // 3. return,即返回結(jié)果為 1
2.r = t (= 5) // 1. 賦值,r 取值 5
func() { // 2. 執(zhí)行 defer 函數(shù),執(zhí)行后 t = 10,但 r = 5
t = t + 5
}()
return r // 3. return r,即返回 5
3.r = 1 // 1. 賦值, r 取值 1
func(r int) { // 2. 執(zhí)行 defer 函數(shù),但作為函數(shù)參數(shù)傳入(緩存值為0)
r = r + 5 // 執(zhí)行后 r = 0 + 5 = 5,但這是局部變量,函數(shù)外仍是 1
}(r)
return r // 3. return r, 即返回 1
踩坑點:
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
注意,上面這段代碼的返回值是匿名的,所以結(jié)果返回0。
現(xiàn)在我們再以2個例子來做總結(jié)和鞏固:
type Person struct {
age int
}
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person.age = 29
}
參考答案及解析:29 29 28。變量 person 是一個指針變量 。
1.person.age 此時是將 28 當(dāng)做 defer 函數(shù)的參數(shù),會把 28 緩存在棧中,等到最后執(zhí)行該 defer 語句的時候取出,即輸出 28;
2.defer 緩存的是結(jié)構(gòu)體 Person{28} 的地址,最終 Person{28} 的 age 被重新賦值為 29,所以 defer 語句最后執(zhí)行的時候,依靠緩存的地址取出的 age 便是 29,即輸出 29;
3.閉包引用,輸出 29;
又由于 defer 的執(zhí)行順序為先進后出,即 3 2 1,所以輸出 29 29 28。
type Person struct {
age int
}
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person = &Person{29}
}
參考答案及解析:29 28 28。這道題在第 19 天題目的基礎(chǔ)上做了一點點小改動,前一題最后一行代碼
person.age = 29 是修改引用對象的成員 age,這題最后一行代碼 person = &Person{29} 是修改引用對象本身,來看看有什么區(qū)別。
1.person.age 這一行代碼跟之前含義是一樣的,此時是將 28 當(dāng)做 defer 函數(shù)的參數(shù),會把 28 緩存在棧中,等到最后執(zhí)行該 defer 語句的時候取出,即輸出 28;
2.defer 緩存的是結(jié)構(gòu)體 Person{28} 的地址,這個地址指向的結(jié)構(gòu)體沒有被改變,最后 defer 語句后面的函數(shù)執(zhí)行的時候取出仍是 28;
3.閉包引用,person 的值已經(jīng)被改變,指向結(jié)構(gòu)體 Person{29},所以輸出 29.
由于 defer 的執(zhí)行順序為先進后出,即 3 2 1,所以輸出 29 28 28。
最后打個小廣告:最近朋友建立了一個倉庫,記錄golang開發(fā)中踩過的坑和遇到的問題,歡迎大家把自己遇到的問題記錄下來,共同進步!
倉庫地址:https://github.com/remake100/go-study
到此這篇關(guān)于golang的閉包與defer的文章就介紹到這了,更多相關(guān)golang defer閉包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang開發(fā)Go依賴管理工具dep安裝驗證實現(xiàn)過程
這篇文章主要為大家介紹了Golang開發(fā)Go依賴管理工具dep安裝驗證及初始化一系列實現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2021-11-11
Go?WEB框架使用攔截器驗證用戶登錄狀態(tài)實現(xiàn)
這篇文章主要為大家介紹了Go?WEB框架使用攔截器驗證用戶登錄狀態(tài)實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07
Go語言構(gòu)建流數(shù)據(jù)pipeline的示例詳解
Go的并發(fā)原語可以輕松構(gòu)建流數(shù)據(jù)管道,從而高效利用?I/O?和多個?CPU,?本文展示了此類pipelines的示例,強調(diào)了操作失敗時出現(xiàn)的細微之處,并介紹了干凈地處理失敗的技術(shù),希望對大家有所幫助2024-02-02
輕松入門:使用Golang開發(fā)跨平臺GUI應(yīng)用
Golang是一種強大的編程語言,它的并發(fā)性和高性能使其成為開發(fā)GUI桌面應(yīng)用的理想選擇,Golang提供了豐富的標(biāo)準(zhǔn)庫和第三方庫,可以輕松地創(chuàng)建跨平臺的GUI應(yīng)用程序,通過使用Golang的GUI庫,開發(fā)人員可以快速構(gòu)建具有豐富用戶界面和交互功能的應(yīng)用程序,需要的朋友可以參考下2023-10-10

