微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

golang-闭包实例详解

一、闭包的概念

闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。闭包的概念可以直接百度或者google详细搜索详细,这里主要是通过几个实例来对闭包进行详解。

二、闭包实例详解

实例1

//函数片段
func add(base int) func(int) int {

	fmt.Printf("%p\n",&base)  //打印变量地址,可以看出来 内部函数时对外部传入参数的引用

	f := func(i int) int {
		fmt.Printf("%p\n",&base)
		base += i
		return base
	}

	return f
}

//由 main 函数作为程序入口点启动
func main() {
	t1 := add(10)
	fmt.Println(t1(1),t1(2))
	
	t2 := add(100)
	fmt.Println(t2(1),t2(2))

}

程序执行结果如下所示:


根据程序的执行结果可以看出来,内部函数是对外部变量引用。

重点:延迟调用有些知识点有异曲同工的地方,函数体内某个变量作为defer匿名函数的参数,则在定义defer时已获得值拷贝,否则引用某个变量的地址(引用拷贝)。代码片段如下所示:

//由 main 函数作为程序入口点启动
func main() {
	x,y := 1,2

	defer func(a int){
		fmt.Println("defer x,y = ",a,y) //y为闭包引用
	}(x) //x值拷贝 调用时传入参数

	x += 100
	y += 200

	fmt.Println(x,y)
}

程序执行结果:


//由 main 函数作为程序入口点启动
func main() {
	for i := 0; i < 3; i++ {
		//多次注册延迟调用,相反顺序执行
		defer func(){
			fmt.Println(i) //闭包引用局部变量
		}()

		fmt.Print(i)
		if i == 2 {
			fmt.Printf("\n")
		}
	}
}

程序执行结果:


实例二

返回多个内部函数,程序片段如下所示:

//返回加减函数,重点:内部函数时对外部变量的引用
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(100)
	
	fmt.Println(f1(1),f2(2)) //执行顺序:f1 f2 println
	fmt.Println(f1(3),f2(4))
	fmt.Println(f1(5),f2(6))
	fmt.Println(f1(7),f2(8))
}

程序执行结果:


实例三

涉及 goroutine 时的情况,程序片段如下所示:

//由 main 函数作为程序入口点启动
func main() {

	for i:=0; i<5; i++ {
		go func(){
			fmt.Println(i) //i变量值也是引用.创建5个线程执行函数, for循环执行过程中可能执行完的时候,线程刚好处于i的某个值。 
		}()
		
	}
	time.Sleep(time.Second * 1)
}

程序运行结果:


代码改进:

//由 main 函数作为程序入口点启动
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)
}

程序执行结果:


通过这三个示例的结果,闭包中的值是对源变量的引用。指向的是变量的当前值。

实例四:闭包错误引起的死锁

最初使用 goroutine 的时候,曾遇到一个程序死锁的问题。思来想去看代码都觉得问题,最后发现是错误使用闭包导致的问题。

具体的代码如下所示:

func main() {
	//创建slice
	cs := make([](chan int),10)
	for i := 0; i < len(cs); i++ {
		cs[i] = make(chan int)
	}

	for i := range cs { go func() { cs[i] <- i //创建线程,但是i是引用外部变量,不一定等线程执行的时候就是当前i值 }() }

	for i := 0; i < len(cs); i++ {
		t := <-cs[i]        //读取值的时候,可能会出现一只阻塞的情况
		fmt.Println(t)
	}
}

红色部分是有问题的代码。主要功能就是 创建10个线程执行函数,并向channal写入值。由于goroutine还没有开始,i的值已经跑到了最大9,使得这几个goroutine都取的i=9这个值,从而都向cs[9]发消息,导致执行t := <-cs[i]时,cs[0]、cs[1]、cs[2] … 都阻塞起来了,从而导致了死锁。

代码修改如下:两种方法修改

for i := range cs {
        go func(index int) {
            cs[index] <- index
        }(i)
}
ch := make(chan int)
for i := range cs {
	go func() {
		ch <- 1
		cs[i] <- i
	}()
	<- ch
}

参考网址:https://blog.csdn.net/libing_thinking/article/details/49889491

值得看的博客网址:https://www.jianshu.com/p/fa21e6fada70

https://studygolang.com/articles/9747

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐