golang-閉包實例詳解--未完

閉包

概念

閉包函數:聲明在一個函數中的函數,叫做閉包函數。

閉包:內部函數總是可以訪問其所在的外部函數中聲明的參數和變量,即使在其外部函數被返回(壽命終結)了之後。

特點

讓外部訪問函數內部變量成爲可能;

局部變量會常駐在內存中;

可以避免使用全局變量,防止全局變量污染;

會造成內存泄漏(有一塊內存空間被長期佔用,而不被釋放)

閉包的創建

閉包就是可以創建一個獨立的環境,每個閉包裏面的環境都是獨立的,互不干擾。閉包會發生內存泄漏,**每次外部函數執行的時候,外部函數的引用地址不同,都會重新創建一個新的地址。**但凡是當前活動對象中又被內部子集引用的數據,那麼這個時候,這個數據不刪除,保留一根

閉包實例

示例一

  • 返回一個內部函數
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
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章