真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Golang中的閉包怎么實(shí)現(xiàn)

這篇文章主要介紹了Golang中的閉包怎么實(shí)現(xiàn)的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Golang中的閉包怎么實(shí)現(xiàn)文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

目前成都創(chuàng)新互聯(lián)已為超過(guò)千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機(jī)、網(wǎng)站托管維護(hù)、企業(yè)網(wǎng)站設(shè)計(jì)、婁煩網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。

1、什么是閉包?

在真正講述閉包之前,我們先鋪墊一點(diǎn)知識(shí)點(diǎn):

  • 函數(shù)式編程

  • 函數(shù)作用域

  • 作用域的繼承關(guān)系

1.1 前提知識(shí)鋪墊

1.2.1 函數(shù)式編程

函數(shù)式編程是一種編程范式,看待問(wèn)題的一種方式,每一個(gè)函數(shù)都是為了用小函數(shù)組織成為更大的函數(shù),函數(shù)的參數(shù)也是函數(shù),函數(shù)返回的也是函數(shù)。我們常見(jiàn)的編程范式有:

  • 命令式編程:

    • 主要思想為:關(guān)注計(jì)算機(jī)執(zhí)行的步驟,也就是一步一步告訴計(jì)算機(jī)先做什么再做什么。

    • 先把解決問(wèn)題步驟規(guī)范化,抽象為某種算法,然后編寫具體的算法去實(shí)現(xiàn),一般只要支持過(guò)程化編程范式的語(yǔ)言,我們都可以稱為過(guò)程化編程語(yǔ)言,比如 BASIC,C 等。

  • 聲明式編程:

    • 主要思想為:告訴計(jì)算機(jī)應(yīng)該做什么,但是不指定具體要怎么做,比如 SQL,網(wǎng)頁(yè)編程的 HTML,CSS。

  • 函數(shù)式編程:

    • 只關(guān)注做什么而不關(guān)注怎么做,有一絲絲聲明式編程的影子,但是更加側(cè)重于”函數(shù)是第一位“的原則,也就是函數(shù)可以出現(xiàn)在任何地方,參數(shù)、變量、返回值等等。

函數(shù)式編程可以認(rèn)為是面向?qū)ο缶幊痰膶?duì)立面,一般只有一些編程語(yǔ)言會(huì)強(qiáng)調(diào)一種特定的編程方式,大多數(shù)的語(yǔ)言都是多范式語(yǔ)言,可以支持多種不同的編程方式,比如 JavaScript ,Go 等。

函數(shù)式編程是一種思維方式,將電腦運(yùn)算視為函數(shù)的計(jì)算,是一種寫代碼的方法論,其實(shí)我應(yīng)該聊函數(shù)式編程,然后再聊到閉包,因?yàn)殚]包本身就是函數(shù)式編程里面的一個(gè)特點(diǎn)之一。

在函數(shù)式編程中,函數(shù)是頭等對(duì)象,意思是說(shuō)一個(gè)函數(shù),既可以作為其它函數(shù)的輸入?yún)?shù)值,也可以從函數(shù)中返回值,被修改或者被分配給一個(gè)變量。(維基百科)

一般純函數(shù)編程語(yǔ)言是不允許直接使用程序狀態(tài)以及可變對(duì)象的,函數(shù)式編程本身就是要避免使用 共享狀態(tài),可變狀態(tài),盡可能避免產(chǎn)生 副作用

函數(shù)式編程一般具有以下特點(diǎn):

  • 函數(shù)是第一等公民:函數(shù)的地位放在第一位,可以作為參數(shù),可以賦值,可以傳遞,可以當(dāng)做返回值。

  • 沒(méi)有副作用:函數(shù)要保持純粹獨(dú)立,不能修改外部變量的值,不修改外部狀態(tài)。

  • 引用透明:函數(shù)運(yùn)行不依賴外部變量或者狀態(tài),相同的輸入?yún)?shù),任何情況,所得到的返回值都應(yīng)該是一樣的。

1.2.2 函數(shù)作用域

作用域(scope),程序設(shè)計(jì)概念,通常來(lái)說(shuō),一段程序代碼中所用到的名字并不總是有效/可用的,而限定這個(gè)名字的可用性的代碼范圍就是這個(gè)名字的作用域。

通俗易懂的說(shuō),函數(shù)作用域是指函數(shù)可以起作用的范圍。函數(shù)有點(diǎn)像盒子,一層套一層,作用域我們可以理解為是個(gè)封閉的盒子,也就是函數(shù)的局部變量,只能在盒子內(nèi)部使用,成為獨(dú)立作用域。

Golang中的閉包怎么實(shí)現(xiàn)

函數(shù)內(nèi)的局部變量,出了函數(shù)就跳出了作用域,找不到該變量。(里層函數(shù)可以使用外層函數(shù)的局部變量,因?yàn)橥鈱雍瘮?shù)的作用域包括了里層函數(shù)),比如下面的 innerTmep 出了函數(shù)作用域就找不到該變量,但是 outerTemp 在內(nèi)層函數(shù)里面還是可以使用。

Golang中的閉包怎么實(shí)現(xiàn)

不管是任何語(yǔ)言,基本存在一定的內(nèi)存回收機(jī)制,也就是回收用不到的內(nèi)存空間,回收的機(jī)制一般和上面說(shuō)的函數(shù)的作用域是相關(guān)的,局部變量出了其作用域,就有可能被回收,如果還被引用著,那么就不會(huì)被回收。

1.2.3 作用域的繼承關(guān)系

所謂作用域繼承,就是前面說(shuō)的小盒子可以繼承外層大盒子的作用域,在小盒子可以直接取出大盒子的東西,但是大盒子不能取出小盒子的東西,除非發(fā)生了逃逸(逃逸可以理解為小盒子的東西給出了引用,大盒子拿到就可以使用)。一般而言,變量的作用域有以下兩種:

  • 全局作用域:作用于任何地方

  • 局部作用域:一般是代碼塊,函數(shù)、包內(nèi),函數(shù)內(nèi)部聲明/定義的變量叫局部變量作用域僅限于函數(shù)內(nèi)部

1.2 閉包的定義

“多數(shù)情況下我們并不是先理解后定義,而是先定義后理解“,先下定義,讀不懂沒(méi)關(guān)系

閉包(closure)是一個(gè)函數(shù)以及其捆綁的周邊環(huán)境狀態(tài)(lexical environment,詞法環(huán)境)的引用的組合。 換而言之,閉包讓開(kāi)發(fā)者可以從內(nèi)部函數(shù)訪問(wèn)外部函數(shù)的作用域。 閉包會(huì)隨著函數(shù)的創(chuàng)建而被同時(shí)創(chuàng)建。

一句話表述:

閉包=函數(shù)+引用環(huán)境閉包 = 函數(shù) + 引用環(huán)境

以上定義找不到 Go語(yǔ)言 這幾個(gè)字眼,聰明的同學(xué)肯定知道,閉包是和語(yǔ)言無(wú)關(guān)的,不是 JavaScript 特有的,也不是 Go 特有的,而是函數(shù)式編程語(yǔ)言的特有的,是的,你沒(méi)有看錯(cuò),任何支持函數(shù)式編程的語(yǔ)言都支持閉包,Go 和 JavaScript 就是其中之二, 目前 Java 目前版本也是支持閉包的,但是有些人可能認(rèn)為不是完美的閉包,詳細(xì)情況文中討論。

1.3 閉包的寫法

1.3.1 初看閉包

下面是一段閉包的代碼:

import "fmt"

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	var sum = func() int {
		fmt.Println("求結(jié)果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	return sum
}

輸出的結(jié)果:

先獲取函數(shù),不求結(jié)果
等待一會(huì)
求結(jié)果...
結(jié)果: 15

可以看出,里面的sum() 方法可以引用外部函數(shù)lazySum() 的參數(shù)以及局部變量,在lazySum()返回函數(shù)sum() 的時(shí)候,相關(guān)的參數(shù)和變量都保存在返回的函數(shù)中,可以之后再進(jìn)行調(diào)用。

上面的函數(shù)或許還可以更進(jìn)一步,體現(xiàn)出捆綁函數(shù)和其周圍的狀態(tài),我們加上一個(gè)次數(shù) count

import "fmt"
func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())
	fmt.Println("結(jié)果:", sumFunc())
	fmt.Println("結(jié)果:", sumFunc())
}func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結(jié)果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	return sum
}

上面代碼輸出什么呢?次數(shù)count 會(huì)不會(huì)發(fā)生變化,count明顯是外層函數(shù)的局部變量,但是在內(nèi)存函數(shù)引用(捆綁),內(nèi)層函數(shù)被暴露出去了,執(zhí)行結(jié)果如下:

先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求結(jié)果...
結(jié)果: 15
第 2 次求結(jié)果...
結(jié)果: 15
第 3 次求結(jié)果...
結(jié)果: 15

結(jié)果是count 其實(shí)每次都會(huì)變化,這種情況總結(jié)一下:

  • 函數(shù)體內(nèi)嵌套了另外一個(gè)函數(shù),并且返回值是一個(gè)函數(shù)。

  • 內(nèi)層函數(shù)被暴露出去,被外層函數(shù)以外的地方引用著,形成了閉包。

此時(shí)有人可能有疑問(wèn)了,前面是lazySum()被創(chuàng)建了 1 次,執(zhí)行了 3 次,但是如果是 3 次執(zhí)行都是不同的創(chuàng)建,會(huì)是怎么樣呢?實(shí)驗(yàn)一下:

import "fmt"
func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())

	sumFunc1 := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc1())

	sumFunc2 := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc2())
}func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結(jié)果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	return sum
}

執(zhí)行的結(jié)果如下,每次執(zhí)行都是第 1 次:

先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求結(jié)果...
結(jié)果: 15
先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求結(jié)果...
結(jié)果: 15
先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求結(jié)果...
結(jié)果: 15

從以上的執(zhí)行結(jié)果可以看出:

閉包被創(chuàng)建的時(shí)候,引用的外部變量count就已經(jīng)被創(chuàng)建了 1 份,也就是各自調(diào)用是沒(méi)有關(guān)系的

繼續(xù)拋出一個(gè)問(wèn)題,**如果一個(gè)函數(shù)返回了兩個(gè)函數(shù),這是一個(gè)閉包還是兩個(gè)閉包呢?**下面我們實(shí)踐一下:

一次返回兩個(gè)函數(shù),一個(gè)用于計(jì)算加和的結(jié)果,一個(gè)計(jì)算乘積:

import "fmt"
func main() {
	sumFunc, productSFunc := lazyCalculate([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())
	fmt.Println("結(jié)果:", productSFunc())
}func lazyCalculate(arr []int) (func() int, func() int) {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求加和...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	var product = func() int {
		count++
		fmt.Println("第", count, "次求乘積...")
		result := 0
		for _, v := range arr {
			result = result * v
		}		return result
	}	return sum, product
}

運(yùn)行結(jié)果如下:

先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求加和...
結(jié)果: 15
第 2 次求乘積...
結(jié)果: 0

從上面結(jié)果可以看出,閉包是函數(shù)返回函數(shù)的時(shí)候,不管多少個(gè)返回值(函數(shù)),都是一次閉包,如果返回的函數(shù)有使用外部函數(shù)變量,則會(huì)綁定到一起,相互影響:

Golang中的閉包怎么實(shí)現(xiàn)

閉包綁定了周圍的狀態(tài),我理解此時(shí)的函數(shù)就擁有了狀態(tài),讓函數(shù)具有了對(duì)象所有的能力,函數(shù)具有了狀態(tài)。

1.3.2 閉包中的指針和值

上面的例子,我們閉包中用到的都是數(shù)值,如果我們傳遞指針,會(huì)是怎么樣的呢?

import "fmt"
func main() {
	i := 0
	testFunc := test(&i)
	testFunc()
	fmt.Printf("outer i = %d\n", i)
}func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i = %d\n", *i)	return func() {
		*i = *i + 1
		fmt.Printf("func inner i = %d\n", *i)
	}
}

運(yùn)行結(jié)果如下:

test inner i = 1
func inner i = 2
outer i = 2

可以看出如果是指針的話,閉包里面修改了指針對(duì)應(yīng)的地址的值,也會(huì)影響閉包外面的值。這個(gè)其實(shí)很容易理解,Go 里面沒(méi)有引用傳遞,只有值傳遞,那我們傳遞指針的時(shí)候,也是值傳遞,這里的值是指針的數(shù)值(可以理解為地址值)。

當(dāng)我們函數(shù)的參數(shù)是指針的時(shí)候,參數(shù)會(huì)拷貝一份這個(gè)指針地址,當(dāng)做參數(shù)進(jìn)行傳遞,因?yàn)楸举|(zhì)還是地址,所以內(nèi)部修改的時(shí)候,仍然可以對(duì)外部產(chǎn)生影響。

閉包里面的數(shù)據(jù)其實(shí)地址也是一樣的,下面的實(shí)驗(yàn)可以證明:

func main() {
	i := 0
	testFunc := test(&i)
	testFunc()
	fmt.Printf("outer i address %v\n", &i)
}
func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i address %v\n", i)
	return func() {
		*i = *i + 1
		fmt.Printf("func inner i address %v\n", i)
	}
}

輸出如下, 因此可以推斷出,閉包如果引用外部環(huán)境的指針數(shù)據(jù),只是會(huì)拷貝一份指針地址數(shù)據(jù),而不是拷貝一份真正的數(shù)據(jù)(==先留個(gè)問(wèn)題:拷貝的時(shí)機(jī)是什么時(shí)候呢==):

test inner i address 0xc0003fab98
func inner i address 0xc0003fab98
outer i address 0xc0003fab98

1.3.2 閉包延遲化

上面的例子仿佛都在告訴我們,閉包創(chuàng)建的時(shí)候,數(shù)據(jù)就已經(jīng)拷貝了,但是真的是這樣么?

下面是繼續(xù)前面的實(shí)驗(yàn):

func main() {
	i := 0
	testFunc := test(&i)
	i = i + 100
	fmt.Printf("outer i before testFunc  %d\n", i)
	testFunc()
	fmt.Printf("outer i after testFunc %d\n", i)
}func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i = %d\n", *i)
		return func() {
		*i = *i + 1
		fmt.Printf("func inner i = %d\n", *i)
	}
}

我們?cè)趧?chuàng)建閉包之后,把數(shù)據(jù)改了,之后執(zhí)行閉包,答案肯定是真實(shí)影響閉包的執(zhí)行,因?yàn)樗鼈兌际侵羔槪际侵赶蛲环輸?shù)據(jù):

test inner i = 1
outer i before testFunc  101
func inner i = 102
outer i after testFunc 102

假設(shè)我們換個(gè)寫法,讓閉包外部環(huán)境中的變量在聲明閉包函數(shù)的之后,進(jìn)行修改:

import "fmt"

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	count := 0
	var sum = func() int {
		fmt.Println("第", count, "次求結(jié)果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	count = count + 100
	return sum
}

實(shí)際執(zhí)行結(jié)果,count 會(huì)是修改后的值:

等待一會(huì)
第 100 次求結(jié)果...
結(jié)果: 15

這也證明了,實(shí)際上閉包并不會(huì)在聲明var sum = func() int {...}這句話之后,就將外部環(huán)境的count綁定到閉包中,而是在函數(shù)返回閉包函數(shù)的時(shí)候,才綁定的,這就是延遲綁定。

如果還沒(méi)看明白沒(méi)關(guān)系,我們?cè)賮?lái)一個(gè)例子:

func main() {
	funcs := testFunc(100)
	for _, v := range funcs {
		v()
	}
}
func testFunc(x int) []func() {
	var funcs []func()
	values := []int{1, 2, 3}
	for _, val := range values {
		funcs = append(funcs, func() {
			fmt.Printf("testFunc val = %d\n", x+val)
		})
	}
	return funcs
}

上面的例子,我們閉包返回的是函數(shù)數(shù)組,本意我們想入每一個(gè)val 都不一樣,但是實(shí)際上val都是一個(gè)值,==也就是執(zhí)行到return funcs 的時(shí)候(或者真正執(zhí)行閉包函數(shù)的時(shí)候)才綁定的val值==(關(guān)于這一點(diǎn),后面還有個(gè)Demo可以證明),此時(shí)val的值是最后一個(gè)3,最終輸出結(jié)果都是103:

testFunc val = 103
testFunc val = 103
testFunc val = 103

以上兩個(gè)例子,都是閉包延遲綁定的問(wèn)題導(dǎo)致,這也可以說(shuō)是 feature,到這里可能不少同學(xué)還是對(duì)閉包綁定外部變量的時(shí)機(jī)有疑惑,到底是返回閉包函數(shù)的時(shí)候綁定的呢?還是真正執(zhí)行閉包函數(shù)的時(shí)候才綁定的呢?

下面的例子可以有效的解答:

import (
	"fmt"
	"time"
)

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會(huì)")
	fmt.Println("結(jié)果:", sumFunc())
	time.Sleep(time.Duration(3) * time.Second)
	fmt.Println("結(jié)果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數(shù),不求結(jié)果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結(jié)果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	go func() {
		time.Sleep(time.Duration(1) * time.Second)
		count = count + 100
		fmt.Println("go func 修改后的變量 count:", count)
	}()
	return sum
}

輸出結(jié)果如下:

先獲取函數(shù),不求結(jié)果
等待一會(huì)
第 1 次求結(jié)果...
結(jié)果: 15
go func 修改后的變量 count: 101
第 102 次求結(jié)果...
結(jié)果: 15

第二次執(zhí)行閉包函數(shù)的時(shí)候,明顯count被里面的go func()修改了,也就是調(diào)用的時(shí)候,才真正的獲取最新的外部環(huán)境,但是在聲明的時(shí)候,就會(huì)把環(huán)境預(yù)留保存下來(lái)。

其實(shí)本質(zhì)上,Go Routine的匿名函數(shù)的延遲綁定就是閉包的延遲綁定,上面的例子中,go func(){}獲取到的就是最新的值,而不是原始值0。

總結(jié)一下上面的驗(yàn)證點(diǎn):

  • 閉包每次返回都是一個(gè)新的實(shí)例,每個(gè)實(shí)例都有一份自己的環(huán)境。

  • 同一個(gè)實(shí)例多次執(zhí)行,會(huì)使用相同的環(huán)境。

  • 閉包如果逃逸的是指針,會(huì)相互影響,因?yàn)榻壎ǖ氖侵羔?,相同指針的?nèi)容修改會(huì)相互影響。

  • 閉包并不是在聲明時(shí)綁定的值,聲明后只是預(yù)留了外部環(huán)境(逃逸分析),真正執(zhí)行閉包函數(shù)時(shí),會(huì)獲取最新的外部環(huán)境的值(也稱為延遲綁定)。

  • Go Routine的匿名函數(shù)的延遲綁定本質(zhì)上就是閉包的延遲綁定。

2、閉包的好處與壞處?

2.1 好處

純函數(shù)沒(méi)有狀態(tài),而閉包則是讓函數(shù)輕松擁有了狀態(tài)。但是凡事都有兩面性,一旦擁有狀態(tài),多次調(diào)用,可能會(huì)出現(xiàn)不一樣的結(jié)果,就像是前面測(cè)試的 case 中一樣。那么問(wèn)題來(lái)了:

Q:如果不支持閉包的話,我們想要函數(shù)擁有狀態(tài),需要怎么做呢?

A: 需要使用全局變量,讓所有函數(shù)共享同一份變量。

但是我們都知道全局變量有以下的一些特點(diǎn)(在不同的場(chǎng)景,優(yōu)點(diǎn)會(huì)變成缺點(diǎn)):

  • 常駐于內(nèi)存之中,只要程序不停會(huì)一直在內(nèi)存中。

  • 污染全局,大家都可以訪問(wèn),共享的同時(shí)不知道誰(shuí)會(huì)改這個(gè)變量。

閉包可以一定程度優(yōu)化這個(gè)問(wèn)題:

  • 不需要使用全局變量,外部函數(shù)局部變量在閉包的時(shí)候會(huì)創(chuàng)建一份,生命周期與函數(shù)生命周期一致,閉包函數(shù)不再被引用的時(shí)候,就可以回收了。

  • 閉包暴露的局部變量,外界無(wú)法直接訪問(wèn),只能通過(guò)函數(shù)操作,可以避免濫用。

除了以上的好處,像在 JavaScript 中,沒(méi)有原生支持私有方法,可以靠閉包來(lái)模擬私有方法,因?yàn)殚]包都有自己的詞法環(huán)境。

2.2 壞處

函數(shù)擁有狀態(tài),如果處理不當(dāng),會(huì)導(dǎo)致閉包中的變量被誤改,但這是編碼者應(yīng)該考慮的問(wèn)題,是預(yù)期中的場(chǎng)景。

閉包中如果隨意創(chuàng)建,引用被持有,則無(wú)法銷毀,同時(shí)閉包內(nèi)的局部變量也無(wú)法銷毀,過(guò)度使用閉包會(huì)占有更多的內(nèi)存,導(dǎo)致性能下降。一般而言,能共享一份閉包(共享閉包局部變量數(shù)據(jù)),不需要多次創(chuàng)建閉包函數(shù),是比較優(yōu)雅的方式。

3、閉包怎么實(shí)現(xiàn)的?

從上面的實(shí)驗(yàn)中,我們可以知道,閉包實(shí)際上就是外部環(huán)境的逃逸,跟隨著閉包函數(shù)一起暴露出去。

我們用以下的程序進(jìn)行分析:

import "fmt"

func testFunc(i int) func() int {
	i = i * 2
	testFunc := func() int {
		i++
		return i
	}
	i = i * 2
	return testFunc
}
func main() {
	test := testFunc(1)
	fmt.Println(test())
}

執(zhí)行結(jié)果如下:

5

先看看逃逸分析,用下面的命令行可以查看:

 go build --gcflags=-m main.go

Golang中的閉包怎么實(shí)現(xiàn)

可以看到 變量 i被移到堆中,也就是本來(lái)是局部變量,但是發(fā)生逃逸之后,從棧里面放到堆里面,同樣的 test()函數(shù)由于是閉包函數(shù),也逃逸到堆上。

下面我們用命令行來(lái)看看匯編代碼:

go tool compile -N -l -S main.go

生成代碼比較長(zhǎng),我截取一部分:

"".testFunc STEXT size=218 args=0x8 locals=0x38 funcid=0x0 align=0x0
        0x0000 00000 (main.go:5)        TEXT    "".testFunc(SB), ABIInternal, $56-8
        0x0000 00000 (main.go:5)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:5)        PCDATA  $0, $-2
        0x0004 00004 (main.go:5)        JLS     198
        0x000a 00010 (main.go:5)        PCDATA  $0, $-1
        0x000a 00010 (main.go:5)        SUBQ    $56, SP
        0x000e 00014 (main.go:5)        MOVQ    BP, 48(SP)
        0x0013 00019 (main.go:5)        LEAQ    48(SP), BP
        0x0018 00024 (main.go:5)        FUNCDATA        $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $1, gclocals·d571c0f6cf0af59df28f76498f639cf2(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $5, "".testFunc.arginfo1(SB)
        0x0018 00024 (main.go:5)        MOVQ    AX, "".i+64(SP)
        0x001d 00029 (main.go:5)        MOVQ    $0, "".~r0+16(SP)
        0x0026 00038 (main.go:5)        LEAQ    type.int(SB), AX
        0x002d 00045 (main.go:5)        PCDATA  $1, $0
        0x002d 00045 (main.go:5)        CALL    runtime.newobject(SB)
        0x0032 00050 (main.go:5)        MOVQ    AX, "".&i+40(SP)
        0x0037 00055 (main.go:5)        MOVQ    "".i+64(SP), CX
        0x003c 00060 (main.go:5)        MOVQ    CX, (AX)
        0x003f 00063 (main.go:6)        MOVQ    "".&i+40(SP), CX
        0x0044 00068 (main.go:6)        MOVQ    "".&i+40(SP), DX
        0x0049 00073 (main.go:6)        MOVQ    (DX), DX
        0x004c 00076 (main.go:6)        SHLQ    $1, DX
        0x004f 00079 (main.go:6)        MOVQ    DX, (CX)
        0x0052 00082 (main.go:7)        LEAQ    type.noalg.struct { F uintptr; "".i *int }(SB), AX
        0x0059 00089 (main.go:7)        PCDATA  $1, $1
        0x0059 00089 (main.go:7)        CALL    runtime.newobject(SB)
        0x005e 00094 (main.go:7)        MOVQ    AX, ""..autotmp_3+32(SP)
        0x0063 00099 (main.go:7)        LEAQ    "".testFunc.func1(SB), CX
        0x006a 00106 (main.go:7)        MOVQ    CX, (AX)
        0x006d 00109 (main.go:7)        MOVQ    ""..autotmp_3+32(SP), CX
        0x0072 00114 (main.go:7)        TESTB   AL, (CX)
        0x0074 00116 (main.go:7)        MOVQ    "".&i+40(SP), DX
        0x0079 00121 (main.go:7)        LEAQ    8(CX), DI
        0x007d 00125 (main.go:7)        PCDATA  $0, $-2
        0x007d 00125 (main.go:7)        CMPL    runtime.writeBarrier(SB), $0
        0x0084 00132 (main.go:7)        JEQ     136
        0x0086 00134 (main.go:7)        JMP     142
        0x0088 00136 (main.go:7)        MOVQ    DX, 8(CX)
        0x008c 00140 (main.go:7)        JMP     149
        0x008e 00142 (main.go:7)        CALL    runtime.gcWriteBarrierDX(SB)
        0x0093 00147 (main.go:7)        JMP     149
        0x0095 00149 (main.go:7)        PCDATA  $0, $-1
        0x0095 00149 (main.go:7)        MOVQ    ""..autotmp_3+32(SP), CX
        0x009a 00154 (main.go:7)        MOVQ    CX, "".testFunc+24(SP)
        0x009f 00159 (main.go:11)       MOVQ    "".&i+40(SP), CX
        0x00a4 00164 (main.go:11)       MOVQ    "".&i+40(SP), DX
        0x00a9 00169 (main.go:11)       MOVQ    (DX), DX
        0x00ac 00172 (main.go:11)       SHLQ    $1, DX
        0x00af 00175 (main.go:11)       MOVQ    DX, (CX)
        0x00b2 00178 (main.go:12)       MOVQ    "".testFunc+24(SP), AX
        0x00b7 00183 (main.go:12)       MOVQ    AX, "".~r0+16(SP)
        0x00bc 00188 (main.go:12)       MOVQ    48(SP), BP
        0x00c1 00193 (main.go:12)       ADDQ    $56, SP
        0x00c5 00197 (main.go:12)       RET
        0x00c6 00198 (main.go:12)       NOP
        0x00c6 00198 (main.go:5)        PCDATA  $1, $-1
        0x00c6 00198 (main.go:5)        PCDATA  $0, $-2
        0x00c6 00198 (main.go:5)        MOVQ    AX, 8(SP)
        0x00cb 00203 (main.go:5)        CALL    runtime.morestack_noctxt(SB)
        0x00d0 00208 (main.go:5)        MOVQ    8(SP), AX
        0x00d5 00213 (main.go:5)        PCDATA  $0, $-1
        0x00d5 00213 (main.go:5)        JMP     0

可以看到閉包函數(shù)實(shí)際上底層也是用結(jié)構(gòu)體new創(chuàng)建出來(lái)的:

Golang中的閉包怎么實(shí)現(xiàn)

使用的就是堆上面的 i

Golang中的閉包怎么實(shí)現(xiàn)

也就是返回函數(shù)的時(shí)候,實(shí)際上返回結(jié)構(gòu)體,結(jié)構(gòu)體里面記錄了函數(shù)的引用環(huán)境。

4、淺聊一下

4.1 Java 支不支持閉包?

網(wǎng)上有很多種看法,實(shí)際上 Java 雖然暫時(shí)不支持返回函數(shù)作為返參,但是Java 本質(zhì)上還是實(shí)現(xiàn)了閉包的概念的,所使用的的方式是內(nèi)部類的形式,因?yàn)槭莾?nèi)部類,所以相當(dāng)于自帶了一個(gè)引用環(huán)境,算是一種不完整的閉包。

目前有一定限制,比如是 final 聲明的,或者是明確定義的值,才可以進(jìn)行傳遞:

Golang中的閉包怎么實(shí)現(xiàn)

4.2 函數(shù)式編程的前景怎么樣?

下面是Wiki的內(nèi)容:

函數(shù)式編程長(zhǎng)期以來(lái)在學(xué)術(shù)界流行,但幾乎沒(méi)有工業(yè)應(yīng)用。造成這種局面的主因是函數(shù)式編程常被認(rèn)為嚴(yán)重耗費(fèi)CPU和存儲(chǔ)器資源[18] ,這是由于在早期實(shí)現(xiàn)函數(shù)式編程語(yǔ)言時(shí)并沒(méi)有考慮過(guò)效率問(wèn)題,而且面向函數(shù)式編程特性,如保證參照透明性等,要求獨(dú)特的數(shù)據(jù)結(jié)構(gòu)和算法。[19]

然而,最近幾種函數(shù)式編程語(yǔ)言已經(jīng)在商業(yè)或工業(yè)系統(tǒng)中使用[20],例如:

  • Erlang,它由瑞典公司愛(ài)立信在20世紀(jì)80年代后期開(kāi)發(fā),最初用于實(shí)現(xiàn)容錯(cuò)電信系統(tǒng)。此后,它已在Nortel、Facebook、électricité de France和WhatsApp等公司作為流行語(yǔ)言創(chuàng)建一系列應(yīng)用程序。[21][22]

  • Scheme,它被用作早期Apple Macintosh計(jì)算機(jī)上的幾個(gè)應(yīng)用程序的基礎(chǔ),并且最近已應(yīng)用于諸如訓(xùn)練模擬軟件和望遠(yuǎn)鏡控制等方向。

  • OCaml,它于20世紀(jì)90年代中期推出,已經(jīng)在金融分析,驅(qū)動(dòng)程序驗(yàn)證,工業(yè)機(jī)器人編程和嵌入式軟件靜態(tài)分析等領(lǐng)域得到了商業(yè)應(yīng)用。

  • Haskell,它雖然最初是作為一種研究語(yǔ)言,也已被一系列公司應(yīng)用于航空航天系統(tǒng),硬件設(shè)計(jì)和網(wǎng)絡(luò)編程等領(lǐng)域。

其他在工業(yè)中使用的函數(shù)式編程語(yǔ)言包括多范型的Scala[23]、F#,還有Wolfram語(yǔ)言、Common Lisp、Standard ML和Clojure等。

關(guān)于“Golang中的閉包怎么實(shí)現(xiàn)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Golang中的閉包怎么實(shí)現(xiàn)”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


文章題目:Golang中的閉包怎么實(shí)現(xiàn)
文章起源:http://weahome.cn/article/pjhpde.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部