Pelaksanaan asas penutupan fungsi Go
Penutupan fungsi bukanlah perbendaharaan kata lanjutan untuk kebanyakan pembaca, jadi apakah penutupan itu? Berikut ialah petikan daripada definisi di Wiki:
Penutupan ialah rekod yang menyimpan fungsi bersama-sama dengan persekitaran. Persekitaran ialah pemetaan yang mengaitkan setiap pembolehubah bebas fungsi (pembolehubah yang digunakan secara tempatan, tetapi ditakrifkan dalam skop yang disertakan ) dengan nilai atau rujukan yang mana nama itu diikat semasa penutupan dibuat.
Ringkasnya, penutupan ialah entiti yang terdiri daripada fungsi dan persekitaran rujukan. Dalam proses pelaksanaan, penutupan sering dilaksanakan dengan memanggil fungsi luaran dan mengembalikan fungsi dalaman mereka. Antaranya, persekitaran rujukan merujuk kepada pemetaan pembolehubah bebas dalam fungsi luaran (digunakan oleh fungsi dalaman, tetapi ditakrifkan dalam fungsi luaran). Fungsi dalaman memperkenalkan pembolehubah bebas luaran supaya pembolehubah ini tidak akan dikeluarkan atau dipadam walaupun ia meninggalkan persekitaran fungsi luaran Fungsi dalaman yang dikembalikan masih menyimpan maklumat ini.
Petikan ini mungkin tidak mudah difahami, jadi mari kita gunakan contoh sahaja.
1package main 2 3import "fmt" 4 5func outer() func() int { 6 x := 1 7 return func() int { 8 x++ 9 return x 10 } 11} 12 13func main() { 14 closure := outer() 15 fmt.Println(closure()) 16 fmt.Println(closure()) 17} 18 19// output 202 213
Seperti yang anda lihat, dua ciri dalam Go (fungsi ialah warga kelas pertama dan sokongan untuk fungsi tanpa nama) memudahkan untuk melaksanakan penutupan.
Dalam contoh di atas, <code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);"><span style="font-size: 15px;letter-spacing: 1px;">closure</span>
是闭包函数,变量x就是引用环境,它们的组合就是闭包实体。<span style="font-size: 15px;letter-spacing: 1px;">x</span>
本是<span style="font-size: 15px;letter-spacing: 1px;">outer</span>
函数之内,匿名函数之外的局部变量。在正常函数调用结束之后,<span style="font-size: 15px;letter-spacing: 1px;">x</span>
就会随着函数栈的销毁而销毁。但是由于匿名函数的引用,<span style="font-size: 15px;letter-spacing: 1px;">outer</span>
返回的函数对象会一直持有<span style="font-size: 15px;letter-spacing: 1px;">x</span>
变量。这造成了每次调用闭包<code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);"><span style="font-size: 15px;letter-spacing: 1px;">closure</span>,<span style="font-size: 15px;letter-spacing: 1px;">x</span>
变量都会得到累加。
这里和普通的函数调用不一样:局部变量<span style="font-size: 15px;letter-spacing: 1px;">x</span>
closure ialah fungsi penutupan dan pembolehubah x ialah rujukan persekitaran. Gabungan mereka adalah entiti penutupan.
<p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">x<span style="font-size: 15px;letter-spacing: 1px;">
Ini ialah
</p>luar🎜
🎜Pembolehubah setempat dalam fungsi dan di luar fungsi tanpa nama. Selepas panggilan fungsi biasa tamat, 🎜🎜x🎜
🎜 akan dimusnahkan apabila timbunan fungsi dimusnahkan. Tetapi disebabkan rujukan fungsi tanpa nama, 🎜🎜outer🎜
🎜Objek fungsi yang dikembalikan akan sentiasa dipegang🎜 🎜x🎜
🎜 pembolehubah. Ini menghasilkan setiap panggilan ke penutupan 🎜🎜closure🎜
🎜, 🎜🎜x🎜
🎜pembolehubah akan dikumpul. 🎜🎜🎜🎜Ini berbeza daripada panggilan fungsi biasa: pembolehubah setempat🎜🎜x🎜
🎜 tidak mengikut Fungsi panggilan tamat dan hilang. Jadi, mengapa ini? 🎜🎜🎜🎜🎜🎜🎜实现原理
我们不妨从汇编入手,将上述代码稍微修改一下
1package main 2 3func outer() func() int { 4 x := 1 5 return func() int { 6 x++ 7 return x 8 } 9} 10 11func main() { 12 _ := outer() 13}
得到编译后的汇编语句如下。
1$ go tool compile -S -N -l main.go 2"".outer STEXT size=181 args=0x8 locals=0x28 3 0x0000 00000 (main.go:3) TEXT "".outer(SB), ABIInternal, $40-8 4 ... 5 0x0021 00033 (main.go:3) MOVQ $0, "".~r0+48(SP) 6 0x002a 00042 (main.go:4) LEAQ type.int(SB), AX 7 0x0031 00049 (main.go:4) MOVQ AX, (SP) 8 0x0035 00053 (main.go:4) PCDATA $1, $0 9 0x0035 00053 (main.go:4) CALL runtime.newobject(SB) 10 0x003a 00058 (main.go:4) MOVQ 8(SP), AX 11 0x003f 00063 (main.go:4) MOVQ AX, "".&x+24(SP) 12 0x0044 00068 (main.go:4) MOVQ $1, (AX) 13 0x004b 00075 (main.go:5) LEAQ type.noalg.struct { F uintptr; "".x *int }(SB), AX 14 0x0052 00082 (main.go:5) MOVQ AX, (SP) 15 0x0056 00086 (main.go:5) PCDATA $1, $1 16 0x0056 00086 (main.go:5) CALL runtime.newobject(SB) 17 0x005b 00091 (main.go:5) MOVQ 8(SP), AX 18 0x0060 00096 (main.go:5) MOVQ AX, ""..autotmp_4+16(SP) 19 0x0065 00101 (main.go:5) LEAQ "".outer.func1(SB), CX 20 0x006c 00108 (main.go:5) MOVQ CX, (AX) 21 ...
首先,我们发现不一样的是 <span style="font-size: 15px;letter-spacing: 1px;">x:=1</span>
会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span>
函数(内置<span style="font-size: 15px;letter-spacing: 1px;">new</span>
函数的底层函数,它返回数据类型指针)。在正常函数局部变量的定义时,例如
1package main 2 3func add() int { 4 x := 100 5 x++ 6 return x 7} 8 9func main() { 10 _ = add() 11}
我们能发现 <span style="font-size: 15px;letter-spacing: 1px;">x:=100</span>
是不会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span>
函数的,它对应的汇编是如下
1"".add STEXT nosplit size=58 args=0x8 locals=0x10 2 0x0000 00000 (main.go:3) TEXT "".add(SB), NOSPLIT|ABIInternal, $16-8 3 ... 4 0x000e 00014 (main.go:3) MOVQ $0, "".~r0+24(SP) 5 0x0017 00023 (main.go:4) MOVQ $100, "".x(SP) // x:=100 6 0x001f 00031 (main.go:5) MOVQ $101, "".x(SP) 7 0x0027 00039 (main.go:6) MOVQ $101, "".~r0+24(SP) 8 ...
留着疑问,继续往下看。我们发现有以下语句
1 0x004b 00075 (main.go:5) LEAQ type.noalg.struct { F uintptr; "".x *int }(SB), AX 2 0x0052 00082 (main.go:5) MOVQ AX, (SP) 3 0x0056 00086 (main.go:5) PCDATA $1, $1 4 0x0056 00086 (main.go:5) CALL runtime.newobject(SB)
我们看到 <span style="font-size: 15px;letter-spacing: 1px;">type.noalg.struct { F uintptr; "".x *int }(SB)</span>
,这其实就是定义的一个闭包数据类型,它的结构表示如下
1type closure struct { 2 F uintptr // 函数指针,代表着内部匿名函数 3 x *int // 自由变量x,代表着对外部环境的引用 4}
之后,在通过 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span>
函数创建了闭包对象。而且由于 <span style="font-size: 15px;letter-spacing: 1px;">LEAQ xxx yyy</span>
代表的是将 <span style="font-size: 15px;letter-spacing: 1px;">xxx</span>
指针,传递给 <span style="font-size: 15px;letter-spacing: 1px;">yyy</span>
,因此 <span style="font-size: 15px;letter-spacing: 1px;">outer</span>
函数最终的返回,其实是闭包结构体对象指针。在《详解逃逸分析》一文中,我们详细地描述了Go编译器的逃逸分析机制,对于这种函数返回暴露给外部指针的情况,很明显,闭包对象会被分配至堆上,变量x也会随着对象逃逸至堆。这就很好地解释了为什么<span style="font-size: 15px;letter-spacing: 1px;">x</span>
变量没有随着函数栈的销毁而消亡。
我们可以通过逃逸分析来验证我们的结论
1$ go build -gcflags '-m -m -l' main.go 2# command-line-arguments 3./main.go:6:3: outer.func1 capturing by ref: x (addr=true assign=true width=8) 4./main.go:5:9: func literal escapes to heap: 5./main.go:5:9: flow: ~r0 = &{storage for func literal}: 6./main.go:5:9: from func literal (spill) at ./main.go:5:9 7./main.go:5:9: from return func literal (return) at ./main.go:5:2 8./main.go:4:2: x escapes to heap: 9./main.go:4:2: flow: {storage for func literal} = &x: 10./main.go:4:2: from func literal (captured by a closure) at ./main.go:5:9 11./main.go:4:2: from x (reference) at ./main.go:6:3 12./main.go:4:2: moved to heap: x // 变量逃逸 13./main.go:5:9: func literal escapes to heap // 函数逃逸
至此,我相信读者已经明白为什么闭包能持续持有外部变量的原因了。那么,我们来思考上文中留下的疑问,为什么在<span style="font-size: 15px;letter-spacing: 1px;">x:=1</span>
时会调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span>
函数。
我们将上文中的例子改为如下,即删掉 <span style="font-size: 15px;letter-spacing: 1px;">x++</span>
代码
1package main 2 3func outer() func() int { 4 x := 1 5 return func() int { 6 return x 7 } 8} 9 10func main() { 11 _ = outer() 12}
此时,<span style="font-size: 15px;letter-spacing: 1px;">x:=1</span>
处的汇编代码,将不再调用 <span style="font-size: 15px;letter-spacing: 1px;">runtime.newobject</span>
函数,通过逃逸分析也会发现将<span style="font-size: 15px;letter-spacing: 1px;">x</span>
不再逃逸,生成的闭包对象中的<span style="font-size: 15px;letter-spacing: 1px;">x</span>
的将是值类型<span style="font-size: 15px;letter-spacing: 1px;">int</span>
1type closure struct { 2 F uintptr 3 x int 4}
这其实就是Go编译器做得精妙的地方:当闭包内没有对外部变量造成修改时,Go 编译器会将自由变量的引用传递优化为直接值传递,避免变量逃逸。
总结
函数闭包一点也不神秘,它就是函数和引用环境而组合的实体。在Go中,闭包在底层是一个结构体对象,它包含了函数指针与自由变量。
Mekanisme analisis pelarian pengkompil Go akan memperuntukkan objek penutupan kepada timbunan, supaya pembolehubah bebas tidak akan hilang apabila tindanan fungsi dimusnahkan, dan ia sentiasa boleh wujud bergantung pada entiti penutupan. Oleh itu, kelebihan dan kekurangan penggunaan penutupan adalah jelas: penutupan boleh mengelak daripada menggunakan pembolehubah global dan sebaliknya mengekalkan pembolehubah bebas yang disimpan dalam ingatan untuk jangka masa yang lama, bagaimanapun, pegangan tersirat pembolehubah bebas ini akan menyebabkan masalah apabila digunakan secara tidak wajar pembaziran dan kebocoran ingatan.
Dalam projek sebenar, tidak terdapat banyak senario penggunaan penutupan. Sudah tentu, jika anda menulis penutupan dalam kod anda, sebagai contoh, fungsi panggil balik yang anda tulis membentuk penutupan, anda perlu berhati-hati, jika tidak, masalah penggunaan memori boleh menyebabkan anda mengalami masalah.
Atas ialah kandungan terperinci Pelaksanaan asas penutupan fungsi Go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Permulaan Pantas: Gunakan fungsi bahasa Go untuk melaksanakan fungsi pengecaman imej mudah Dalam perkembangan teknologi hari ini, teknologi pengecaman imej telah menjadi topik hangat. Sebagai bahasa pengaturcaraan yang pantas dan cekap, bahasa Go mempunyai keupayaan untuk melaksanakan fungsi pengecaman imej. Artikel ini akan memberikan pembaca panduan permulaan pantas dengan menggunakan fungsi bahasa Go untuk melaksanakan fungsi pengecaman imej yang mudah. Pertama, kita perlu memasang persekitaran pembangunan bahasa Go. Anda boleh memuat turun versi yang sesuai di laman web rasmi bahasa Go (https://golang.org/)

Mula Pantas: Gunakan fungsi bahasa Go untuk melaksanakan fungsi penyulitan dan penyahsulitan data yang mudah Dalam masyarakat maklumat hari ini, kerahsiaan data telah menjadi sangat penting. Untuk memastikan kerahsiaan data, kami biasanya menggunakan pelbagai algoritma penyulitan untuk menyulitkan data. Dalam artikel ini, kami akan menggunakan fungsi bahasa Go untuk melaksanakan fungsi penyulitan dan penyahsulitan data mudah. Pertama, kita perlu mengimport pakej crypto/cipher untuk menggunakan algoritma penyulitan. Kami akan menggunakan AES (AdvancedEncryptionS

Penutupan fungsi tidak misteri sama sekali Ia adalah entiti yang terdiri daripada fungsi dan persekitaran rujukan. Dalam Go, penutupan ialah objek struktur di bahagian bawah, yang mengandungi penunjuk fungsi dan pembolehubah bebas.

Mula Pantas: Gunakan fungsi bahasa Go untuk melaksanakan fungsi rangkak data yang mudah Dalam era Internet hari ini, pemerolehan dan pemprosesan data menjadi semakin penting. Sebagai kaedah pemerolehan data biasa, rangkak data digunakan secara meluas dalam pelbagai bidang. Dalam artikel ini, saya akan memperkenalkan cara menggunakan fungsi bahasa Go untuk melaksanakan fungsi rangkak data mudah untuk membantu pembaca bermula dengan cepat. Bahasa Go ialah bahasa yang ditaip secara statik Sintaksnya yang ringkas dan prestasi serentak yang cekap menjadikannya pilihan pertama bagi banyak pembangun. Berikut akan memperkenalkan cara melaksanakan fungsi bahasa Go

Dalam program Go, nilai pulangan fungsi adalah sangat penting. Anda mungkin menghadapi masalah di mana fungsi anda mengembalikan nilai yang salah, atau tidak mengembalikan nilai, yang boleh menyebabkan masalah dengan program anda. Keadaan ini boleh berlaku dalam sebarang program saiz, dan dalam artikel ini kita akan membincangkan beberapa kemungkinan penyebab masalah ini. Ralat definisi fungsi Pertama, anda perlu memastikan bahawa fungsi anda ditakrifkan dengan betul. Definisi fungsi harus mengisytiharkan nama fungsi, senarai parameter dan jenis pulangan. Jika anda terlupa jenis pemulangan, Go akan lalai untuk kembali

Garis panduan utama untuk menulis fungsi Go yang cekap dan boleh diselenggara termasuk: pastikan fungsi ringkas dan padat, fokus pada satu tanggungjawab, gunakan tandatangan kaedah yang jelas, semak ralat dan kembalikan maklumat yang jelas, dan gunakan ulasan dokumentasi untuk ulasan. Mengikuti garis panduan ini menghasilkan kod yang lebih jelas, lebih mudah untuk diuji dan lebih mudah diselenggara.

Menulis ujian unit dalam Go membantu memastikan kualiti dan kebolehpercayaan kod. Ujian unit termasuk langkah-langkah seperti mengimport kebergantungan, menyediakan objek, menentukan input dan output, memanggil fungsi, dan menegaskan output. Dengan menggunakan fungsi penegasan daripada pakej ujian anda boleh membandingkan output sebenar dengan output yang dijangkakan. Gunakan arahan yang paling berkesan untuk menjalankan ujian dan pastikan semua ujian lulus untuk memastikan ketepatan kod Go.

Fungsi PHP dan Go mempunyai kedua-dua persamaan dan perbezaan utama. Persamaan: Gunakan ruang nama dan skop untuk menyusun kod. Parameter boleh diluluskan dengan nilai atau rujukan. Biasanya mengembalikan satu atau lebih nilai. Perbezaan: PHP menggunakan sistem jenis dinamik manakala Go menggunakan sistem jenis statik. Fungsi Go menyokong penggunaan nilai lalai dan parameter variadic, manakala PHP tidak. Kedua-dua PHP dan Go menyokong fungsi tanpa nama, tetapi sintaksnya berbeza sedikit.
