首页 > 其他分享 >【go从入门到精通】闭包和陷阱

【go从入门到精通】闭包和陷阱

时间:2024-03-25 10:32:11浏览次数:14  
标签:闭包 入门 int fmt Println func go 函数

 作者简介:

        高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注) 

----------------------------------------------------------------------------------------------------------------

         上一篇文章 【go从入门到精通】匿名函数详解  当时写完,总是感觉意犹未尽,从匿名函数我联想到了另外一个用法————闭包。 

Go闭包的实现原理

        闭包是指一个函数值(函数对象)可以引用在其外部定义的变量。这些变量可以是在函数内部未定义的,但是该函数可以访问和操作这些变量。

         闭包和匿名函数似乎有些像。目前在JavaScript、Go、PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、Ruby、 Python、Lua、objective c、Swift 以及Java8以上等语言中都能找到对闭包不同程度的支持。通过支持闭包的语法可以发现一个特点,他们都有垃圾回收(GC)机制。 

        在go中, 闭包的实现取决于函数的返回类型。具体来说,如果一个函数返回一个函数类型,那么该函数就是一个闭包。

常见使用时机:延长区域变数的生命周期,不会被GC吃掉。


package main

import "fmt"

var foo = func() func() {
	var i int
	return func() {
		i++
		fmt.Println(i)
	}
}()

func main() {
	foo() // 1
	foo() // 2
	foo() // 3
}

输出结果是:

1

2

3         

        在闭包的使用上,不仅可以设置函式回传函式,也可以设置函式回传struct等其他类型状态。

package main
import "fmt"

type counter struct { 
	add   func()
	minus func()
	print func()
}

func main() {
	count := func() counter {
		idx := 0
		return counter{
			func() { idx++ },
			func() { idx-- },
			func() { fmt.Println("idx =", idx) },
		}
	}()
	
	count.add()
	count.print() // idx = 1
	count.add()
	count.print() // idx = 2
	count.minus()
	count.print() // idx = 1
}

 

        闭包函数及其引用环境共同构成一个struct,其中函数部分是闭包函数的代码,引用环境包含闭包函数访问的外部变量。该结构由编译器自动创建,并在调用时传递给闭包函数。

        闭包函数与其引用环境之间的连接是通过指针实现的。当闭包函数引用外部变量时,编译器在引用环境中查找该变量并返回其地址。该地址存储在闭包函数内部,并在调用闭包函数时使用。

我们通过下面代码来看看闭包的使用:


package main

import "fmt"

var G int = 7

func main() {
    // 影响全局变量G,代码块状态持续
    y := func() int {
        fmt.Printf("G: %d, G的地址:%p\n", G, &G)
        G += 1
        return G
    }
    fmt.Println(y(), y)
    fmt.Println(y(), y)
    fmt.Println(y(), y) //y的地址

    // 影响全局变量G,注意z的匿名函数是直接执行,所以结果不变
    z := func() int {
        G += 1
        return G
    }()
    fmt.Println(z, &z)
    fmt.Println(z, &z)
    fmt.Println(z, &z)

    // 影响外层(自由)变量i,代码块状态持续
    var f = N()
    fmt.Println(f(1), &f)
    fmt.Println(f(1), &f)
    fmt.Println(f(1), &f)

    var f1 = N()
    fmt.Println(f1(1), &f1)

}

func N() func(int) int {
    var i int
    return func(d int) int {
        fmt.Printf("i: %d, i的地址:%p\n", i, &i)
        i += d
        return i
    }
}

程序输出:
G: 7, G的地址:0x1547368
8 0xf98e40
G: 8, G的地址:0x1547368
9 0xf98e40
G: 9, G的地址:0x1547368
10 0xf98e40
11 0xc00025a1c0
11 0xc00025a1c0
11 0xc00025a1c0
i: 0, i的地址:0xc00025a1c8
1 0xc0000a8498
i: 1, i的地址:0xc00025a1c8
2 0xc0000a8498
i: 2, i的地址:0xc00025a1c8
3 0xc0000a8498
i: 0, i的地址:0xc00025a1d0
1 0xc0000a84a0

首先强调一点,G是闭包中被捕获的全局变量,因此,对于每一次引用,G的地址都是固定的,i是函数内部局部变量,地址也是固定的,他们都可以被闭包保持状态并修改。还要注意,f和f1是不同的实例,它们的地址是不一样的。

闭包的陷阱

        闭包就像是一个魔术,可以在局部变量的作用域消失后很长一段时间内保留它们。 因为它所做的实际上是返回另一个函数,该函数包装了我们打算保留的变量。我们可以继续在全局范围内使用返回函数中“包含”的那些变量。

// Closure function in Go
func willReturnClosure() func() int {
	var counter int = 0
	return func() int {
		counter += 1
		return counter
	}
}
func main() {
	c := willReturnClosure()
	for i := 0; i < 10; i++ {
		fmt.Println(c())
	}
}

在c()函数中,我们对 func() 的调用将返回局部变量counter,并保持变量的增量状态直到下一次调用,就像你正在处理全局变量一样。

现在,问题是,如果上面的闭包存储在全局变量中,假设我们像这样存储willReturnClosure函数:

// Closure function in Go
func willReturnClosure() func() int {
	var counter int = 0
	return func() int {
		counter += 1
		return counter
	}
}

var c = willReturnClosure()

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(c())
	}
}

与其他全局变量不同,编译器永远不会知道您何时会使用完计数器,并且它永远不会被 GC 释放。它会像《盗梦空间》电影一样徘徊在你的程序的地狱边缘,直到你的全局作用域退出。 应小心处理此内存泄漏。

 

标签:闭包,入门,int,fmt,Println,func,go,函数
From: https://blog.csdn.net/pbymw8iwm/article/details/136915084

相关文章

  • 栅格地图路径规划:基于螳螂搜索算法(Mantis Search Algorithm,MSA)的机器人路径规划(提供MA
        一、机器人路径规划介绍移动机器人(Mobilerobot,MR)的路径规划是移动机器人研究的重要分支之,是对其进行控制的基础。根据环境信息的已知程度不同,路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或局部已知的局部路径规划。随着科技的快速发展以及机器人......
  • Django csrf跨站请求伪造,校验,CBV忽略与允许csrf校验
    csrf跨站请求伪造钓鱼网站,搭建一个网站与正规网站一模一样的界面,用户进入到我们的网站中,给指定用户转账,汇款确实提交到银行,但是收款人确实我们自己定义的人。本质在钓鱼网站的页面针对对方账户,只给用户提供一个没有name属性的普通input框,在内部则隐藏一个已经写好的name和value......
  • Django 中间件以及自定义中间件
    Django中间件Django中间件是Django的门户请求来的时候需要先经过中间件才能达到真正的Django后端响应走的时候最后也要经过中间件才能发送出去MIDDLEWARE=['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddle......
  • djangoCIA报价平台的设计与实现(源码+mysql+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:随着信息技术的飞速发展,互联网已经深入到我们日常生活的方方面面。在众多的应用场景中,报价系统作为商业交易的重要环节,扮演着至关重要的角色。尤其是在CIA(Ce......
  • djangoAndroid共享停车位(源码+mysql+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:随着城市化进程的加速,汽车已成为人们日常生活中不可或缺的交通工具。然而,在许多城市中,由于停车位数量有限,停车难成为了一大问题。为了解决这一问题,共享停车......
  • Django cookie与session,CBV如何添加装饰器
    cookie与session简介会话跟踪技术什么是会话跟踪?我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某......
  • 【附源码】django计算机毕业设计web的诗词信息管理平台(源码+mysql+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:在当今信息化时代,诗词作为中华民族传统文化的瑰宝,承载着丰富的历史和文化价值。然而,随着科技的发展和生活节奏的加快,人们对于诗词的接触和传承逐渐减少,尤其......
  • 【附源码】django计算机毕业设计web技术的养老服务平台(源码+mysql+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义养老服务平台开发设计背景:随着社会老龄化的加剧,养老问题逐渐成为社会关注的热点。老年人作为社会的重要组成部分,他们的生活质量和幸福感直接关系到社会的和谐与稳定......
  • Django Forms组件,展示用户输入不合规的提示信息,钩子函数
    DjangoForms组件,展示用户输入不合规的提示信息,钩子函数前戏:使用form表单,用户输入特定信息,比如:金瓶,输入框右侧提示信息,不使用Ajax。前端代码:<body><formaction=""method="post"><p>username:<inputtype="text"name="username"><......
  • 电路入门 ---- 基本放大电路
    1放大的概念2放大电路的性能指标放大电路的性能指标是评估其放大功能和效率的重要参数。以下是一些常见的放大电路性能指标:2.1增益(Gain)增益是衡量放大电路放大效果的重要指标,表示输出信号幅度与输入信号幅度之间的比例关系。增益可以分为电压增益、电流增益和功率增益等不......