閉包
概念
閉包函數:聲明在一個函數中的函數,叫做閉包函數。
閉包:內部函數總是可以訪問其所在的外部函數中聲明的參數和變量,即使在其外部函數被返回(壽命終結)了之後。
特點
讓外部訪問函數內部變量成爲可能;
局部變量會常駐在內存中;
可以避免使用全局變量,防止全局變量污染;
會造成內存泄漏(有一塊內存空間被長期佔用,而不被釋放)
閉包的創建
閉包就是可以創建一個獨立的環境,每個閉包裏面的環境都是獨立的,互不干擾。閉包會發生內存泄漏,**每次外部函數執行的時候,外部函數的引用地址不同,都會重新創建一個新的地址。**但凡是當前活動對象中又被內部子集引用的數據,那麼這個時候,這個數據不刪除,保留一根
閉包實例
示例一
- 返回一個內部函數
package main
import "fmt"
//函數片段
func add(base int) func(int) int { // 1:創建一個新內存, 其地址並命名爲base用來存儲int變量, 然後取出a內存處存放的值,複製一份,放入base內存
fmt.Printf("%p\n", &base) //2:打印變量地址0xc0420080e0, 裏面存儲着10【它是函數拷貝傳值】
f := func(i int) int { //3:定義一個函數,並且用f指向它[f裏面存儲着這個函數的地址]
//7、將1用i存儲起來
fmt.Printf("%p\n", &base) // 2:打印變量地址0xc0420080e0
base += i // 取出base裏面存儲的值 + i裏面存儲的值,然後將結果放入base存儲起來
return base //將base裏面存儲的值返回
}
return f // 4:將函數地址f返回
}
func main() {
aa := 10
fmt.Printf("a的地址:%p\n", &aa) //0xc0420080a8
t1 := add(aa) //5、將add(a)返回的函數地址用一個t1存儲起來
c := t1(1) //6、調用t1指向的函數,傳參爲1[傳值],然後將返回結果用新建的內存c存儲起來
fmt.Println(c, &c) //11 0xc0420540b0
fmt.Println(t1(2)) // 13 t1還是指向原來開闢的空間,此時base=11, 因此11+2
t2 := add(100) //重新開闢一個空間調用add(100),然後將返回的函數地址用一個t2存儲起來,此時base爲新建立的
fmt.Println(t2(1), t2(2))
}
- 返回多個內部函數
package main
import "fmt"
//返回加減函數,重點:內部函數時對外部變量的引用
func calc(base int) (func(int) int, func(int) int) {
fmt.Printf("%p\n", &base)
add := func(i int) int {
fmt.Printf("%p\n", &base)
base += i
return base
}
sub := func(i int) int {
fmt.Printf("%p\n", &base)
base -= i
return base
}
return add, sub
}
//由 main 函數作爲程序入口點啓動
func main() {
f1, f2 := calc(11111)
fmt.Println(f1(1), f2(1)) //執行順序:f1 f2 println
}
示例二
涉及 goroutine 時的情況,程序片段如下所示:
package main
import (
"fmt"
"time"
)
//由 main 函數作爲程序入口點啓動
func main() {
for i:=0; i<5; i++ {
go func(){
fmt.Println(i) //i變量值也是引用.創建5個線程執行函數, for循環執行過程中可能執行完的時候,線程剛好處於i的某個值。
}()
}
time.Sleep(time.Second * 1)
}
每次程序的運行結果都是不一樣的
代碼改進:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 1)
for i:=0; i<5; i++ {
go func(){
ch <- 1
fmt.Println(i)
}()
<- ch
}
time.Sleep(time.Second * 1)
}
會阻塞等待:0-4值一定有
參考:https://blog.csdn.net/li_101357/article/details/80196650
實例三:
package main
import "fmt"
func funA() func(){
var a = 10;
return func (){
fmt.Println(a); // 打印處a存儲的值
}
}
func main() {
var b = funA() //funA()返回一個函數地址,執行一個函數,並用b保存這個地址,此時b指向這個函數
b() // 調用b指向的函數
}
示例四:全局變量VS閉包函數
package main
import "fmt"
func Outer() func(){
var i = 0;
return func() {
i++;
fmt.Println(i)
}
}
func main(){
var c = Outer()
c()
c()
c()
var d = Outer()
d()
d()
d()
}
// 1 2 3 1 2 3
- 全局函數
package main
import "fmt"
var i = 0;
func Outer() func(){
return func() {
i++;
fmt.Println(i)
}
}
func main(){
var c = Outer()
c()
c()
c()
var d = Outer()
d()
d()
d()
}
// 1 2 3 4 5 6
實例六
package main
import "fmt"
func Outer() func() int{
var i = 0;
return func() int{
i++;
return i;
}
}
func main(){
c := Outer() // 返回一個函數
fmt.Println(c()) //1 調用內部函數,並且拷貝i的值,返回
fmt.Println(c()) //2 調用內部函數,並且拷貝i的值,返回
d := c // c裏面存儲着函數地址,c將存儲的函數地址放入d中,此時d和c指向同一個函數
fmt.Println(d()) //3 調用內部函數,並且拷貝i的值,返回
fmt.Println(d()) //4 調用內部函數,並且拷貝i的值,返回
e := Outer() // 新開闢一個內存,用來調用函數。 e存儲着這個內存的地址
fmt.Println(e()) //1 調用內部函數,並且拷貝i的值,返回
fmt.Println(e()) //2 調用內部函數,並且拷貝i的值,返回
fmt.Println(Outer()) //0x496a30
fmt.Println(Outer()()) //1
}