日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

簡單聊聊Go語言里面的閉包_Golang

作者:第十六封 ? 更新時間: 2022-12-16 編程語言

以前寫 Java 的時候,聽到前端同學談論閉包,覺得甚是新奇,后面自己寫了一小段時間 JS,雖只學到皮毛,也大概了解到閉包的概念,現在工作常用語言是 Go,很多優(yōu)雅的代碼中總是有閉包的身影,看來不了解個透是不可能的了,本文讓我來科普(按照自己水平隨便瞎扯)一下:

1、什么是閉包

在真正講述閉包之前,我們先鋪墊一點知識點:

  • 函數式編程
  • 函數作用域
  • 作用域的繼承關系

1.1 前提知識鋪墊

1.1.1 函數式編程

函數式編程是一種編程范式,看待問題的一種方式,每一個函數都是為了用小函數組織成為更大的函數,函數的參數也是函數,函數返回的也是函數。我們常見的編程范式有:

命令式編程:

  • 主要思想為:關注計算機執(zhí)行的步驟,也就是一步一步告訴計算機先做什么再做什么。
  • 先把解決問題步驟規(guī)范化,抽象為某種算法,然后編寫具體的算法去實現,一般只要支持過程化編程范式的語言,我們都可以稱為過程化編程語言,比如 BASIC,C 等。

聲明式編程:

主要思想為:告訴計算機應該做什么,但是不指定具體要怎么做,比如 SQL,網頁編程的 HTML,CSS。

函數式編程:

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

函數式編程可以認為是面向對象編程的對立面,一般只有一些編程語言會強調一種特定的編程方式,大多數的語言都是多范式語言,可以支持多種不同的編程方式,比如 JavaScript ,Go 等。

函數式編程是一種思維方式,將電腦運算視為函數的計算,是一種寫代碼的方法,其實我應該聊函數式編程,然后再聊到閉包,因為閉包本身就是函數式編程里面的一個特點之一。

在函數式編程中,函數是頭等對象,意思是說一個函數,既可以作為其它函數的輸入參數值,也可以從函數中返回值,被修改或者被分配給一個變量。(維基百科)

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

函數式編程一般具有以下特點:

  • 函數是第一等公民:函數的地位放在第一位,可以作為參數,可以賦值,可以傳遞,可以當做返回值。
  • 沒有副作用:函數要保持純粹獨立,不能修改外部變量的值,不修改外部狀態(tài)。
  • 引用透明:函數運行不依賴外部變量或者狀態(tài),相同的輸入參數,任何情況,所得到的返回值都應該是一樣的。

1.1.2 函數作用域

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

通俗易懂的說,函數作用域是指函數可以起作用的范圍。函數有點像盒子,一層套一層,作用域我們可以理解為是個封閉的盒子,也就是函數的局部變量,只能在盒子內部使用,成為獨立作用域。

函數內的局部變量,出了函數就跳出了作用域,找不到該變量。(里層函數可以使用外層函數的局部變量,因為外層函數的作用域包括了里層函數),比如下面的?innerTmep?出了函數作用域就找不到該變量,但是?outerTemp?在內層函數里面還是可以使用。

不管是任何語言,基本存在一定的內存回收機制,也就是回收用不到的內存空間,回收的機制一般和上面說的函數的作用域是相關的,局部變量出了其作用域,就有可能被回收,如果還被引用著,那么就不會被回收。

1.1.3 作用域的繼承關系

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

  • 全局作用域:作用于任何地方
  • 局部作用域:一般是代碼塊,函數、包內,函數內部聲明/定義的變量叫局部變量作用域僅限于函數內部

1.2 閉包的定義

“多數情況下我們并不是先理解后定義,而是先定義后理解“,先下定義,讀不懂沒關系

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

一句話表述:

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

以上定義找不到 Go語言 這幾個字眼,聰明的同學肯定知道,閉包是和語言無關的,不是 JavaScript 特有的,也不是 Go 特有的,而是函數式編程語言的特有的,是的,你沒有看錯,任何支持函數式編程的語言都支持閉包,Go 和 JavaScript 就是其中之二, 目前 Java 目前版本也是支持閉包的,但是有些人可能認為不是完美的閉包,詳細情況文中討論。

1.3 閉包的寫法

1.3.1 初看閉包

下面是一段閉包的代碼:

import "fmt"

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

輸出的結果:

先獲取函數,不求結果
等待一會
求結果...
結果: 15

可以看出,里面的?sum()?方法可以引用外部函數?lazySum()?的參數以及局部變量,在lazySum()返回函數?sum()?的時候,相關的參數和變量都保存在返回的函數中,可以之后再進行調用。

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

import "fmt"

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

func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數,不求結果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	return sum
}

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

先獲取函數,不求結果
等待一會
第 1 次求結果...
結果: 15
第 2 次求結果...
結果: 15
第 3 次求結果...
結果: 15

結果是?count?其實每次都會變化,這種情況總結一下:

  • 函數體內嵌套了另外一個函數,并且返回值是一個函數。
  • 內層函數被暴露出去,被外層函數以外的地方引用著,形成了閉包。

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

import "fmt"

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

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

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

func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數,不求結果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	return sum
}

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

先獲取函數,不求結果
等待一會
第 1 次求結果...
結果: 15
先獲取函數,不求結果
等待一會
第 1 次求結果...
結果: 15
先獲取函數,不求結果
等待一會
第 1 次求結果...
結果: 15

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

閉包被創(chuàng)建的時候,引用的外部變量count就已經被創(chuàng)建了 1 份,也就是各自調用是沒有關系的。

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

一次返回兩個函數,一個用于計算加和的結果,一個計算乘積:

import "fmt"

func main() {
	sumFunc, productSFunc := lazyCalculate([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會")
	fmt.Println("結果:", sumFunc())
	fmt.Println("結果:", productSFunc())
}

func lazyCalculate(arr []int) (func() int, func() int) {
	fmt.Println("先獲取函數,不求結果")
	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
}

運行結果如下:

先獲取函數,不求結果
等待一會
第 1 次求加和...
結果: 15
第 2 次求乘積...
結果: 0

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

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

1.3.2 閉包中的指針和值

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

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

運行結果如下:

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

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

當我們函數的參數是指針的時候,參數會拷貝一份這個指針地址,當做參數進行傳遞,因為本質還是地址,所以內部修改的時候,仍然可以對外部產生影響。

閉包里面的數據其實地址也是一樣的,下面的實驗可以證明:

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)境的指針數據,只是會拷貝一份指針地址數據,而不是拷貝一份真正的數據(先留個問題:拷貝的時機是什么時候呢):

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

1.3.3 閉包延遲化

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

下面是繼續(xù)前面的實驗:

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

我們在創(chuàng)建閉包之后,把數據改了,之后執(zhí)行閉包,答案肯定是真實影響閉包的執(zhí)行,因為它們都是指針,都是指向同一份數據:

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

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

import "fmt"

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

實際執(zhí)行結果,count?會是修改后的值:

等待一會
第 100 次求結果...
結果: 15

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

如果還沒看明白沒關系,我們再來一個例子:

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
}

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

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

以上兩個例子,都是閉包延遲綁定的問題導致,這也可以說是 feature,到這里可能不少同學還是對閉包綁定外部變量的時機有疑惑,到底是返回閉包函數的時候綁定的呢?還是真正執(zhí)行閉包函數的時候才綁定的呢?

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

import (
	"fmt"
	"time"
)

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一會")
	fmt.Println("結果:", sumFunc())
	time.Sleep(time.Duration(3) * time.Second)
	fmt.Println("結果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先獲取函數,不求結果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求結果...")
		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
}

輸出結果如下:

先獲取函數,不求結果
等待一會 第 1 次求結果...
結果: 15
go func 修改后的變量 count: 101
第 102 次求結果...
結果: 15

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

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

總結一下上面的驗證點:

  • 閉包每次返回都是一個新的實例,每個實例都有一份自己的環(huán)境。
  • 同一個實例多次執(zhí)行,會使用相同的環(huán)境。
  • 閉包如果逃逸的是指針,會相互影響,因為綁定的是指針,相同指針的內容修改會相互影響。
  • 閉包并不是在聲明時綁定的值,聲明后只是預留了外部環(huán)境(逃逸分析),真正執(zhí)行閉包函數時,會獲取最新的外部環(huán)境的值(也稱為延遲綁定)。
  • Go Routine的匿名函數的延遲綁定本質上就是閉包的延遲綁定。

2、閉包的好處與壞處

2.1 好處

純函數沒有狀態(tài),而閉包則是讓函數輕松擁有了狀態(tài)。但是凡事都有兩面性,一旦擁有狀態(tài),多次調用,可能會出現不一樣的結果,就像是前面測試的 case 中一樣。那么問題來了:

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

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

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

  • 常駐于內存之中,只要程序不停會一直在內存中。
  • 污染全局,大家都可以訪問,共享的同時不知道誰會改這個變量。

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

  • 不需要使用全局變量,外部函數局部變量在閉包的時候會創(chuàng)建一份,生命周期與函數生命周期一致,閉包函數不再被引用的時候,就可以回收了。
  • 閉包暴露的局部變量,外界無法直接訪問,只能通過函數操作,可以避免濫用。

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

2.2 壞處

函數擁有狀態(tài),如果處理不當,會導致閉包中的變量被誤改,但這是編碼者應該考慮的問題,是預期中的場景。

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

3、閉包怎么實現的

從上面的實驗中,我們可以知道,閉包實際上就是外部環(huá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í)行結果如下:

5

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

go build --gcflags=-m main.go

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

下面我們用命令行來看看匯編代碼:

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

生成代碼比較長,我截取一部分:

"".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

可以看到閉包函數實際上底層也是用結構體new創(chuàng)建出來的:

使用的就是堆上面的i

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

4、淺聊一下

4.1 Java 支不支持閉包

網上有很多種看法,實際上 Java 雖然暫時不支持返回函數作為返參,但是Java 本質上還是實現了閉包的概念的,所使用的的方式是內部類的形式,因為是內部類,所以相當于自帶了一個引用環(huán)境,算是一種不完整的閉包。

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

Stack Overflow上有相關答案:https://stackoverflow.com/questions/5443510/closure-in-java-7

4.2 函數式編程的前景怎么樣

下面是Wiki的內容:

函數式編程長期以來在學術界流行,但幾乎沒有工業(yè)應用。造成這種局面的主因是函數式編程常被認為嚴重耗費CPU和存儲器資源,這是由于在早期實現函數式編程語言時并沒有考慮過效率問題,而且面向函數式編程特性,如保證參照透明性等,要求獨特的數據結構和算法。

然而,最近幾種函數式編程語言已經在商業(yè)或工業(yè)系統中使用,例如:

  • Erlang,它由瑞典公司愛立信在20世紀80年代后期開發(fā),最初用于實現容錯電信系統。此后,它已在Nortel、Facebook、électricité de France和WhatsApp等公司作為流行語言創(chuàng)建一系列應用程序。
  • Scheme,它被用作早期Apple?Macintosh計算機上的幾個應用程序的基礎,并且最近已應用于諸如訓練模擬軟件和望遠鏡控制等方向。
  • OCaml,它于20世紀90年代中期推出,已經在金融分析,驅動程序驗證,工業(yè)機器人編程和嵌入式軟件靜態(tài)分析等領域得到了商業(yè)應用。
  • Haskell,它雖然最初是作為一種研究語言,也已被一系列公司應用于航空航天系統,硬件設計和網絡編程等領域。

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

從我個人的看法,不看好純函數編程,但是函數式編程的思想,我相信以后幾乎每門高級編程需要都會具備,特別期待 Java 擁抱函數式編程。從我自己了解的語言看,像 Go,JavaScript 中的函數式編程的特性,都讓開發(fā)者深愛不已(當然,如果寫出了bug,就是深惡痛疾)。

最近突然火了一波的原因,也是因為世界不停的發(fā)展,內存也越來越大,這個因素的限制幾乎要解放了。

我相信,世界就是絢麗多彩的,要是一種事物統治世界,絕無可能,更多的是百家爭鳴,編程語言或者編程范式也一樣,后續(xù)可能有集大成者,最終最終歷史會篩選出最終符合人類社會發(fā)展的。

原文鏈接:https://www.cnblogs.com/Damaer/p/16910186.html

欄目分類
最近更新