Heim >Backend-Entwicklung >Golang >Eine kurze Analyse der Schließungen in Golang
Bevor wir tatsächlich über Schließungen sprechen, legen wir einige Grundlagen fest:
[Verwandte Empfehlungen: Go-Video-Tutorial]
. 1.1 Vorkenntnisse erforderlich
Funktionale Programmierung ist ein Programmierparadigma, eine Art, Probleme zu betrachten. Jede Funktion ist so konzipiert, dass sie mithilfe kleiner Funktionen in größere Funktionen organisiert werden kann. Die Parameter von Funktionen sind ebenfalls Funktionen. und die von Funktionen zurückgegebenen Funktionen sind auch Funktionen. Unsere gemeinsamen Programmierparadigmen sind:
Funktionale Programmierung kann als das Gegenteil von objektorientierter Programmierung angesehen werden. Im Allgemeinen legen nur einige Programmiersprachen Wert auf eine bestimmte Programmiermethode. Die meisten Sprachen sind Multiparadigmensprachen und können mehrere unterstützen verschiedene Programmiermethoden wie JavaScript, Go usw.
Funktionale Programmierung ist eine Denkweise, die Computeroperationen als die Berechnung von Funktionen betrachtet. Tatsächlich sollte ich über funktionale Programmierung und dann über Abschlüsse sprechen, da Abschlüsse selbst Funktionen sind die Merkmale der formalen Programmierung.
In der funktionalen Programmierung ist eine Funktion ein erstklassiges Objekt, was bedeutet, dass eine Funktion als Eingabeparameterwert für andere Funktionen verwendet werden kann, auch einen Wert von der Funktion zurückgeben, geändert oder zugewiesen werden kann eine Variable. (Wikipedia)
Im Allgemeinen erlauben reine funktionale Programmiersprachen keine direkte Verwendung des Programmstatus und veränderlicher Objekte. Die Verwendung von gemeinsamen Zuständen und Variablenzuständen sowie Nebenwirkungen sind nicht möglich möglich .
Funktionale Programmierung weist im Allgemeinen die folgenden Merkmale auf:Scope (Bereich), ein Programmierkonzept, das im Allgemeinen nicht immer gültig/verfügbar ist und den Codeumfang einschränkt Die Verfügbarkeit eines Namens ist der Umfang des Namens.
Laienhaft ausgedrückt bezieht sich der Funktionsumfang auf den Bereich, in dem eine Funktion arbeiten kann. Eine Funktion ist ein bisschen wie eine Box, eine Ebene in einer anderen. Wir können den Bereich als geschlossene Box verstehen, das heißt, die lokalen Variablen der Funktion können nur innerhalb der Box verwendet werden und zu einem unabhängigen Bereich werden. Die lokale Variable innerhalb der Funktion springt nach dem Verlassen der Funktion aus dem Gültigkeitsbereich und die Variable kann nicht gefunden werden. (Die innere Funktion kann die lokalen Variablen der äußeren Funktion verwenden, da der Umfang der äußeren Funktion die innere Funktion umfasst. Beispielsweise kann das Folgende weiterhin in der inneren Funktion verwendet werden.) innerTmep
出了函数作用域就找不到该变量,但是 outerTemp
Die sogenannte Bereichsvererbung bedeutet, dass die zuvor erwähnte kleine Box den Umfang der äußeren großen Box erben kann. In der kleinen Box können die Dinge in der großen Box direkt übernommen werden heraus, aber die große Kiste kann nicht herausgenommen werden, es sei denn, es kommt zu einer Flucht (Flucht kann als Referenz für die Dinge in der kleinen Kiste verstanden werden, und die große Kiste kann verwendet werden, sobald Sie sie erhalten ). Im Allgemeinen gibt es zwei Arten von Gültigkeitsbereichen von Variablen:
Globaler Gültigkeitsbereich: Wirkt überall
Lokaler Gültigkeitsbereich: Im Allgemeinen Codeblöcke, Funktionen, Pakete, Innere FunktionenDeklaration/Definition Die Variablen werden lokale Variablen genannt , und der Bereich ist auf das Innere der Funktion beschränkt
1.2 Definition des Abschlusses
„In den meisten Fällen verstehen wir nicht zuerst und definieren dann, sondern definieren zuerst und verstehen dann“ , lass es uns zuerst definieren, Es spielt keine Rolle, wenn du es nicht verstehst:
Ein Abschluss ist eine Kombination aus einer Funktion und ihrer gebündelten Referenz auf die umgebende Umgebung (lexikalische Umgebung, lexikalische Umgebung) . Mit anderen Worten: Abschlüsse ermöglichen Entwicklern den Zugriff auf den Umfang einer äußeren Funktion von einer inneren Funktion aus. Abschlüsse werden erstellt, wenn die Funktion erstellt wird.
In einem Satz erklärt:
Die Wörter Go-Sprache können in der obigen Definition nicht gefunden werden. Sie müssen wissen, dass Schließungen nicht nur für JavaScript oder Go gelten, sondern nur für funktionale Programmiersprachen richtig, Jede Sprache, die funktionale Programmierung unterstützt, unterstützt Abschlüsse, Go und JavaScript sind zwei davon. Die aktuelle Version von Java unterstützt auch Abschlüsse, aber einige Leute denken vielleicht, dass es sich nicht um einen perfekten Abschluss handelt. Details werden im Text besprochen . 1.3 So schreiben Sie Abschlüsse Code im Inneren Die >sum()-Methode kann auf die Parameter und lokalen Variablen der externen Funktion lazySum()
verweisen und die Funktion sum() in <code> zurückgeben lazySum()
code> werden die relevanten Parameter und Variablen in der zurückgegebenen Funktion gespeichert und können später aufgerufen werden.
count
hinzu: 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 }Was gibt der obige Code aus? Wird sich die Häufigkeit von
count
ändern? count
ist offensichtlich eine lokale Variable der äußeren Funktion, aber in der Speicherfunktionsreferenz (Bündelung) wird die innere Funktion offengelegt und ausgeführt Das Ergebnis ist wie folgt: 先获取函数,不求结果 等待一会 求结果... 结果: 15Das Ergebnis ist
count
. Tatsächlich ändert es sich jedes Mal:
lazySum()
einmal erstellt und dreimal ausgeführt wurde, aber wenn es dreimal ausgeführt wird wird anders sein. Wie wird es sein? Experiment: Die Ausführungsergebnisse von 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 }sind wie folgt, jede Ausführung erfolgt zum ersten Mal:
先获取函数,不求结果 等待一会 第 1 次求结果... 结果: 15 第 2 次求结果... 结果: 15 第 3 次求结果... 结果: 15
Wenn der Abschluss erstellt wird, zählt die referenzierte externe Variable
wurde bereits 1 Kopie erstellt, das heißt, es spielt keine Rolle, ob Sie jede einzelne aufrufen
sum()
方法可以引用外部函数 lazySum()
的参数以及局部变量,在lazySum()
返回函数 sum()
的时候,相关的参数和变量都保存在返回的函数中,可以之后再进行调用。
上面的函数或许还可以更进一步,体现出捆绑函数和其周围的状态,我们加上一个次数 count
:
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 }
上面代码输出什么呢?次数 count
会不会发生变化,count
明显是外层函数的局部变量,但是在内存函数引用(捆绑),内层函数被暴露出去了,执行结果如下:
先获取函数,不求结果 等待一会 第 1 次求结果... 结果: 15 先获取函数,不求结果 等待一会 第 1 次求结果... 结果: 15 先获取函数,不求结果 等待一会 第 1 次求结果... 结果: 15
结果是 count
其实每次都会变化,这种情况总结一下:
此时有人可能有疑问了,前面是lazySum()
被创建了 1 次,执行了 3 次,但是如果是 3 次执行都是不同的创建,会是怎么样呢?实验一下:
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 次:
先获取函数,不求结果 等待一会 第 1 次求加和... 结果: 15 第 2 次求乘积... 结果: 0
从以上的执行结果可以看出:
闭包被创建的时候,引用的外部变量count
就已经被创建了 1 份,也就是各自调用是没有关系的。
继续抛出一个问题,**如果一个函数返回了两个函数,这是一个闭包还是两个闭包呢?**下面我们实践一下:
一次返回两个函数,一个用于计算加和的结果,一个计算乘积:
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
从上面结果可以看出,闭包是函数返回函数的时候,不管多少个返回值(函数),都是一次闭包,如果返回的函数有使用外部函数变量,则会绑定到一起,相互影响:
闭包绑定了周围的状态,我理解此时的函数就拥有了状态,让函数具有了对象所有的能力,函数具有了状态。
上面的例子,我们闭包中用到的都是数值,如果我们传递指针,会是怎么样的呢?
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) } }
运行结果如下:
test inner i address 0xc0003fab98 func inner i address 0xc0003fab98 outer i address 0xc0003fab98
可以看出如果是指针的话,闭包里面修改了指针对应的地址的值,也会影响闭包外面的值。这个其实很容易理解,Go 里面没有引用传递,只有值传递,那我们传递指针的时候,也是值传递,这里的值是指针的数值(可以理解为地址值)。
当我们函数的参数是指针的时候,参数会拷贝一份这个指针地址,当做参数进行传递,因为本质还是地址,所以内部修改的时候,仍然可以对外部产生影响。
闭包里面的数据其实地址也是一样的,下面的实验可以证明:
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) } }
输出如下, 因此可以推断出,闭包如果引用外部环境的指针数据,只是会拷贝一份指针地址数据,而不是拷贝一份真正的数据(==先留个问题:拷贝的时机是什么时候呢==):
test inner i = 1 outer i before testFunc 101 func inner i = 102 outer i after testFunc 102
上面的例子仿佛都在告诉我们,闭包创建的时候,数据就已经拷贝了,但是真的是这样么?
下面是继续前面的实验:
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 }
我们在创建闭包之后,把数据改了,之后执行闭包,答案肯定是真实影响闭包的执行,因为它们都是指针,都是指向同一份数据:
等待一会 第 100 次求结果... 结果: 15
假设我们换个写法,让闭包外部环境中的变量在声明闭包函数的之后,进行修改:
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 }
实际执行结果,count
会是修改后的值:
testFunc val = 103 testFunc val = 103 testFunc val = 103
这也证明了,实际上闭包并不会在声明var sum = func() int {...}
这句话之后,就将外部环境的 count
Stellen Sie weiterhin eine Frage auf: **Wenn eine Funktion zwei Funktionen zurückgibt, handelt es sich dabei um einen Abschluss oder um zwei Abschlüsse? **Üben wir es unten: Gib zwei Funktionen gleichzeitig zurück, eine dient zur Berechnung des Summenergebnisses und die andere zur Berechnung des Produkts:
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 }🎜Die laufenden Ergebnisse lauten wie folgt: 🎜
先获取函数,不求结果 等待一会 第 1 次求结果... 结果: 15 go func 修改后的变量 count: 101 第 102 次求结果... 结果: 15🎜 Wie aus den obigen Ergebnissen ersichtlich ist, handelt es sich beim Abschluss um eine Funktionsrückgabe. Unabhängig davon, wie viele Rückgabewerte (Funktionen) vorhanden sind, handelt es sich bei allen um einen Abschluss. Wenn die zurückgegebene Funktion externe Funktionsvariablen verwendet, ist dies der Fall miteinander verbunden sein und sich gegenseitig beeinflussen: 🎜🎜🎜🎜Der Abschluss bindet den umgebenden Zustand, ich verstehe das. Die Funktion hat zu diesem Zeitpunkt einen Zustand, sodass die Funktion alle Fähigkeiten des Objekts hat und die Funktion einen Zustand hat. 🎜
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()) }🎜Die laufenden Ergebnisse sind wie folgt: 🎜
5🎜Es ist ersichtlich, dass, wenn es sich um einen Zeiger handelt und der Wert der dem Zeiger entsprechenden Adresse im Abschluss geändert wird, dies auch Auswirkungen auf den Wert außerhalb des Abschlusses hat. Das ist eigentlich leicht zu verstehen. In Go gibt es keine Referenzübergabe, sondern nur eine Wertübergabe. Der Wert wird hier auch als Wert des Zeigers übergeben ). 🎜🎜Wenn der Parameter unserer Funktion ein Zeiger ist, kopiert der Parameter die Zeigeradresse und übergibt sie als Parameter. Da die Essenz immer noch eine Adresse ist, kann sie bei interner Änderung immer noch Auswirkungen auf die Außenseite haben. 🎜🎜Die Daten im Abschluss haben tatsächlich die gleiche Adresse: 🎜
go build --gcflags=-m main.go🎜Die Ausgabe lautet daher wie folgt: Wenn sich der Abschluss auf die Zeigerdaten der externen Umgebung bezieht, ist dies der Fall kopiert nur die Zeiger-Adressdaten und ist keine Kopie der echten Daten (==Lassen Sie zuerst eine Frage: Wann ist der Zeitpunkt des Kopierens==): 🎜
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, -8 0x0000 00000 (main.go:5) CMPQ SP, 16(R14) 0x0004 00004 (main.go:5) PCDATA rrreee, $-2 0x0004 00004 (main.go:5) JLS 198 0x000a 00010 (main.go:5) PCDATA rrreee, $-1 0x000a 00010 (main.go:5) SUBQ , 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 rrreee, gclocals·69c1753bd5f81501d95132d08af04464(SB) 0x0018 00024 (main.go:5) FUNCDATA , gclocals·d571c0f6cf0af59df28f76498f639cf2(SB) 0x0018 00024 (main.go:5) FUNCDATA , "".testFunc.arginfo1(SB) 0x0018 00024 (main.go:5) MOVQ AX, "".i+64(SP) 0x001d 00029 (main.go:5) MOVQ rrreee, "".~r0+16(SP) 0x0026 00038 (main.go:5) LEAQ type.int(SB), AX 0x002d 00045 (main.go:5) PCDATA , rrreee 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 , 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 , 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 rrreee, $-2 0x007d 00125 (main.go:7) CMPL runtime.writeBarrier(SB), rrreee 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 rrreee, $-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 , 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 , SP 0x00c5 00197 (main.go:12) RET 0x00c6 00198 (main.go:12) NOP 0x00c6 00198 (main.go:5) PCDATA , $-1 0x00c6 00198 (main.go:5) PCDATA rrreee, $-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 rrreee, $-1 0x00d5 00213 (main.go:5) JMP 0🎜Nachdem wir den Abschluss erstellt haben, ändern wir die Daten und führen dann den Abschluss aus. Die Antwort muss sein, dass es sich wirklich auf die Ausführung des Abschlusses auswirkt, da es sich bei allen um Zeiger handelt und zeigen Sie auf dieselbe Kopie wird der geänderte Wert sein: 🎜rrreee🎜Dies beweist auch, dass der Abschluss den
der externen Umgebung nach der Deklaration von <code>var sum = func() int {...} tatsächlich nicht ändert /code> count
ist an den Abschluss gebunden, aber es ist gebunden, wenn die Funktion die Abschlussfunktion zurückgibt. Dies ist eine 🎜verzögerte Bindung🎜. 🎜如果还没看明白没关系,我们再来一个例子:
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
都是一个值,==也就是执行到return funcs
的时候(或者真正执行闭包函数的时候)才绑定的 val
值==(关于这一点,后面还有个Demo可以证明),此时 val
的值是最后一个 3
,最终输出结果都是 103
:
testFunc val = 103 testFunc val = 103 testFunc val = 103
以上两个例子,都是闭包延迟绑定的问题导致,这也可以说是 feature,到这里可能不少同学还是对闭包绑定外部变量的时机有疑惑,到底是返回闭包函数的时候绑定的呢?还是真正执行闭包函数的时候才绑定的呢?
下面的例子可以有效的解答:
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
第二次执行闭包函数的时候,明显 count
被里面的 go func()
修改了,也就是调用的时候,才真正的获取最新的外部环境,但是在声明的时候,就会把环境预留保存下来。
其实本质上,Go Routine的匿名函数的延迟绑定就是闭包的延迟绑定,上面的例子中,go func(){}
获取到的就是最新的值,而不是原始值0
。
总结一下上面的验证点:
2.1 好处
纯函数没有状态,而闭包则是让函数轻松拥有了状态。但是凡事都有两面性,一旦拥有状态,多次调用,可能会出现不一样的结果,就像是前面测试的 case 中一样。那么问题来了:
Q:如果不支持闭包的话,我们想要函数拥有状态,需要怎么做呢?
A: 需要使用全局变量,让所有函数共享同一份变量。
但是我们都知道全局变量有以下的一些特点(在不同的场景,优点会变成缺点):
闭包可以一定程度优化这个问题:
除了以上的好处,像在 JavaScript 中,没有原生支持私有方法,可以靠闭包来模拟私有方法,因为闭包都有自己的词法环境。
2.2 坏处
函数拥有状态,如果处理不当,会导致闭包中的变量被误改,但这是编码者应该考虑的问题,是预期中的场景。
闭包中如果随意创建,引用被持有,则无法销毁,同时闭包内的局部变量也无法销毁,过度使用闭包会占有更多的内存,导致性能下降。一般而言,能共享一份闭包(共享闭包局部变量数据),不需要多次创建闭包函数,是比较优雅的方式。
从上面的实验中,我们可以知道,闭包实际上就是外部环境的逃逸,跟随着闭包函数一起暴露出去。
我们用以下的程序进行分析:
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()) }
执行结果如下:
5
先看看逃逸分析,用下面的命令行可以查看:
go build --gcflags=-m main.go
可以看到 变量 i
被移到堆中,也就是本来是局部变量,但是发生逃逸之后,从栈里面放到堆里面,同样的 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
创建出来的:
Verwenden Sie den i
auf dem Heap: i
:
也就是返回函数的时候,实际上返回结构体,结构体里面记录了函数的引用环境。
4.1 Java 支不支持闭包?
网上有很多种看法,实际上 Java 虽然暂时不支持返回函数作为返参,但是Java 本质上还是实现了闭包的概念的,所使用的的方式是内部类的形式,因为是内部类,所以相当于自带了一个引用环境,算是一种不完整的闭包。
目前有一定限制,比如是 final
4.1 Unterstützt Java Schließungen? Im Internet gibt es viele Meinungen. Obwohl Java derzeit keine Rückgabefunktionen als Rückgabeparameter unterstützt, implementiert Java im Wesentlichen das Konzept des Abschlusses und die verwendete Methode liegt in der Form einer inneren Klasse vor ist eine interne Klasse und entspricht daher dem Einbringen einer Referenzumgebung, was als unvollständiger Abschluss angesehen wird.
Es gibt derzeit bestimmte Einschränkungen, zum Beispiel nur, wenn es mit final
deklariert wird oder ein klar definierter Wert übergeben werden kann:
Es gibt entsprechende Antworten auf Stack Overflow:stackoverflow.com/questions /5…
4.2 Wie sieht die Zukunft der funktionalen Programmierung aus?
🎜Hier ist, was das Wiki sagt: 🎜🎜🎜Funktionale Programmierung ist in der Wissenschaft seit langem beliebt, hat aber nur wenige industrielle Anwendungen. Der Hauptgrund für diese Situation ist, dass funktionale Programmierung oft als stark verbrauchend auf CPU- und Speicherressourcen angesehen wird [🎜18]🎜 Dies liegt daran, dass Effizienzaspekte bei der frühen Implementierung funktionaler Programmiersprachen nicht berücksichtigt wurden, und aufgrund der Eigenschaften funktionaler Programmiersprachen Programmierung, um beispielsweise „Referenztransparenz“ usw. sicherzustellen, erfordert einzigartige Datenstrukturen und Algorithmen. [🎜19]🎜🎜🎜Allerdings werden in letzter Zeit mehrere funktionale Programmiersprachen in kommerziellen oder industriellen Systemen verwendet [🎜20]🎜, zum Beispiel: 🎜
- Erlang, das Ende der 1980er Jahre von der schwedischen Firma Ericsson entwickelt wurde, diente ursprünglich der Implementierung fehlertoleranter Telekommunikationssysteme. Seitdem wird es als beliebte Sprache zum Erstellen einer Reihe von Anwendungen von Unternehmen wie Nortel, Facebook, Électricité de France und WhatsApp verwendet. [21][22]
- Schema, das als Grundlage für mehrere Anwendungen auf frühen Apple MacintoshComputern diente und kürzlich auf Dinge wie Trainingssimulationssoftware und Teleskopsteuerungsrichtung angewendet wurde .
- OCaml, das Mitte der 1990er Jahre auf den Markt kam, hat kommerzielle Anwendungen in Bereichen wie Finanzanalyse, Fahrerverifizierung, Industrieroboterprogrammierung und statischer Analyse eingebetteter Software gefunden.
- Haskell, obwohl es ursprünglich als Forschungssprache verwendet wurde, wurde es auch von einer Reihe von Unternehmen in Bereichen wie Luft- und Raumfahrtsystemen, Hardwaredesign und Netzwerkprogrammierung verwendet.
Weitere funktionale Programmiersprachen, die in der Industrie verwendet werden, sind das Multiparadigma Scala[23], F# sowie Wolfram Language, Common Lisp, Standard ML und Clojure Warte.
Aus meiner persönlichen Sicht bin ich nicht optimistisch, was die reine funktionale Programmierung angeht, aber ich glaube, dass fast jeder fortgeschrittene Programmierbedarf in Zukunft die Idee der funktionalen Programmierung haben wird. Ich freue mich besonders darauf, Java anzunehmen funktionale Programmierung. Von den Sprachen, die ich kenne, sind die funktionalen Programmierfunktionen in Go und JavaScript bei Entwicklern sehr beliebt (natürlich werden Sie sie hassen, wenn Sie Fehler schreiben).
Der Grund, warum es in letzter Zeit plötzlich populär geworden ist, liegt auch darin, dass sich die Welt weiterentwickelt und das Gedächtnis immer größer wird. Die Einschränkungen dieses Faktors werden nahezu aufgehoben.
Ich glaube, dass es absolut unmöglich ist, dass eine Sache die Welt beherrscht. Das Gleiche gilt für Programmiersprachen oder Programmierparadigmen In der Zukunft wird die Geschichte diejenigen herausfiltern, die der Entwicklung der menschlichen Gesellschaft entsprechen.
Weitere Kenntnisse zum Thema Programmierung finden Sie unter: Programmiervideos! !
Das obige ist der detaillierte Inhalt vonEine kurze Analyse der Schließungen in Golang. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!