golang中的闭包

闭包

简介

作用:缩小变量作用域,减少对全局变量的污染

闭包又是什么?你可以想象一下,在一个函数中存在对外来标识符的引用。所谓的外来标识符,既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的

 

一个函数捕获了和他在同一个作用域的其他常量和变量.这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量.

它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用他,这些变量就还会存在.

在go里面,所有的匿名函数都是闭包

func main() {

   a := 10
   str := "mike"

   //匿名函数,没有函数名字,函数定义没有调用
   f1 := func() {
      fmt.Println("a = ", a)
      fmt.Println("str = ", str)
   }

   //调用
   f1()

   //给一个函数类型起别名
   type FuncType func()   //函数没有参数没有返回值
   //声明变量
   var f2 FuncType
   f2 = f1
   f2()

   //定义匿名函数,同时调用
   func() {
      fmt.Printf("a = %d, str = %s\n", a, str)
   }() //后面的()代表调用此匿名函数

   //带参数的匿名函数
   f3 := func(i, j int) {
      fmt.Printf("a = %d, str = %s\n", a, str)
   }

   f3(1, 2)

   //有参数有返回值
   x, y := func(i, j int) (max, min int) {
      if i > j {
         max = i
         min = j
      } else {
         max = j
         min = i
      }
      return
   }(10, 20)
   fmt.Print(x, y)
}

闭包以引用的方式捕获外部变量

func main() {
   a := 10
   str := "nike"
   func() {
      a = 666
      str = "go"
      fmt.Printf("内部: a = %d, str = %s\n", a, str)
   }()    //()代表直接调用
   fmt.Printf("外部: a = %d, str =%s\n", a, str)
}

输出

内部: a = 666, str = go
外部: a = 666, str =go

闭包保存变量

func test02() func() int {
   var x int  //没有初始化,值为0

   return func() int {
      x++
      return x * x
   }
}

func main() {
   //返回函数类型
   f := test02()
   fmt.Println(f())
   fmt.Println(f())
   fmt.Println(f())
}

使用

Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。

在上面例子中(这里重新贴下代码,和上面代码一样):

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

如pos := adder()的adder()表示返回了一个闭包,并赋值给了pos,同时,这个被赋值给了pos的闭包函数被绑定在sum变量上,因此pos闭包函数里的变量sum和neg变量里的sum毫无关系。

func adder() func(int) intfunc(int) int表示adder()的输出值的类型是func(int) int这样一个函数

没有闭包的时候,函数就是一次性买卖,函数执行完毕后就无法再更改函数中变量的值(应该是内存释放了);有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态,因此可以后期更改函数中变量的值(因为这样就不会被go给回收内存了,会一直缓存在那里)。

比如,实现一个计算功能:一个数从0开始,每次加上自己的值和当前循环次数(当前第几次,循环从0开始,到9,共10次),然后*2,这样迭代10次:

没有闭包的时候这么写:

func abc(x int) int {
    return x * 2
}

func main() {
    var a int
    for i := 0; i < 10; i ++ {
        a = abc(a+i)
        fmt.Println(a)
    }
}

如果用闭包可以这么写:

func abc() func(int) int {
    res := 0
    return func(x int) int {
        res = (res + x) * 2
        return res
    }
}

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

2种写法输出值都是:

0
2
8
22
52
114
240
494
1004
2026

从上面例子可以看出闭包的3个好处:

  1. 不是一次性消费,被引用声明后可以重复调用,同时变量又只限定在函数里,同时每次调用不是从初始值开始(函数里长期存储变量)

    这有点像使用面向对象的感觉,实例化一个类,这样这个类里的所有方法、属性都是为某个人私有独享的。但比面向对象更加的轻量化

  2. 用了闭包后,主函数就变得简单了,把算法封装在一个函数里,使得主函数省略了a=abc(a+i)这种麻烦事了

  3. 变量污染少,因为如果没用闭包,就会为了传递值到函数里,而在函数外部声明变量,但这样声明的变量又会被下面的其他函数或代码误改。

关于闭包的第一个好处,再啰嗦举个例子

  1. 若不用闭包,则容易对函数外的变量误操作(误操作别人),例:
var A int = 1
func main() {
    foo := func () {
        A := 2
        fmt.Println(A)
    }
    foo()
    fmt.Println(A)
}

输出:

2
1

如果手误将A := 2写成了A = 2,那么输出就是:

2
2

即会影响外部变量A

  1. 为了将某一个私有的值传递到某个函数里,就需要在函数外声明这个值,但是这样声明会导致这个值在其他函数里也可见了(别人误操作我),例:
func main() {
    A := 1
    foo := func () int {
        return A + 1
    }
    B := 1
    bar := func () int {
        return B + 2
    }
    fmt.Println(foo())
    fmt.Println(bar())
}

输出:

2
3

在bar里是可以对变量A做操作的,一个不小心就容易误修改变量A

**结论:函数外的变量只能通过参数传递进去,不要通过全局变量的方式的渠道传递进去,当函数内能读取到的变量越多,出错概率(误操
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盼盼编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值