七叶笔记 » golang编程 » Golang:闭包

Golang:闭包

南京夫子庙

闭包是高级语言通常都会包含的一种语法,Python和Go中都支持这个语法。

那么,究竟什么是闭包呢?

首先我们从匿名函数开始讲起:

匿名函数

同样Python和Go都支持匿名函数,以Go为例,我们看一个简单的例子吧:

 package main

import "fmt"

func main() {
        lambda := func() {
                fmt.Println("I'm an anonymous function")
        }
        lambda()
}  

运行结果:

 ➜  closure git:(master) ✗ go run lambda.go 
I'm an anonymous function  

函数作为一等公民(First class citizen),可以把它赋给一个变量,这样就可以做到实时更新:

 package main

import "fmt"

func main() {
        var functor func() int
        functor = func() int {
                return 10
        }
        fmt.Println(functor())
        functor = func() int {
                return 15
        }
        fmt.Println(functor())
        functor = func() int {
                return 20
        }
        fmt.Println(functor())
}  

运行结果:

 ➜  closure git:(master) ✗ go run functor_realtime_update.go
10
15
20  

闭包

如果一个内部嵌套函数中,引用了外部定义的变量,那么我们就称这个函数为“闭包”,同时我们称被引用的变量为“引用变量”或“自由变量”。

下面我们看一个引用外部变量的嵌套函数:

 package main

import "fmt"

func main() {
        var nestedFunctorInMain func()
        counter := 0
        nestedFunctorInMain = func() {
                counter += 1
                fmt.Println(counter)
        }
        nestedFunctorInMain()
        nestedFunctorInMain()
        nestedFunctorInMain()
}  

运行结果:

 ➜  closure git:(master) ✗ go run nested_function.go 
1
2
3  

这里的“nestedFunctorInMain”就是一个闭包,它嵌套在入口函数“main”中,并且引用了外部变量“counter”。

那闭包有什么特殊的用处呢?最简单的,利用闭包特性,我们可以达到数据隔离的目的:

 package main

import "fmt"

func main() {
  counter := newCounter()
  fmt.Println(counter())
  fmt.Println(counter())
}

func newCounter() func() int {
  n := 0
  return func() int {
    n += 1
    return n
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run data_isolation.go 
1
2  

之前的嵌套函数的例子里面,我们的闭包使用了main函数中的外部变量,但是这个变量使用范围过大,其他函数也可以更新它,在本例中,我们设置在新函数中设置变量“n”,而嵌套匿名函数引用它,即使“newCounter”函数执行完毕,变量依然存在闭包中,且外部无法访问,这样就很方便地达到了数据隔离的目的。

闭包中遇到的常见问题

  • defer和go使用函数调用作为参数

假设我们有个setup和teardown的功能需要实现,即在函数执行前利用setup做一些初始化操作,然后在执行结束后,做环境清理工作:

 package main

import "fmt"

func main() {
  defer func() {
    fmt.Prinltn("teardown")
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run defer_need_func_call.go 
# command-line-arguments
./defer_need_func_call.go:3:8: imported and not used: "fmt"
./defer_need_func_call.go:8:4: expression in defer must be function call  

因为“defer”和“go”关键字的参数是函数调用,而不是函数,所以按照本例的写法,编译代码时会报错:“expression in defer must be function call”。

我们改进一下:

 package main

import "fmt"

func main() {
        defer setup()
}

func setup() func() {
  fmt.Println("pretend to set things up")

  return func() {
    fmt.Println("pretend to tear things down")
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run defer_need_func_call.go 
pretend to set things up  

“teardown”并未得到执行!为什么?

仔细看本例中代码,“setup”函数返回匿名函数,即“teardown”,但是“teardown”并未有机会得到显式执行,所以代码未运行到。

再改进一下:

 package main

import "fmt"

func main() {
        defer setup()()
}

func setup() func() {
  fmt.Println("pretend to set things up")

  return func() {
    fmt.Println("pretend to tear things down")
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run defer_need_func_call.go 
pretend to set things up
pretend to tear things down  

显式执行返回的匿名函数,“teardown”部分得到执行。

  • “for”循环中的变量是引用传递而非值传递

这个问题很容易让人迷惑,我们先看个例子:

 package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    functions = append(functions, func() {
      fmt.Println(i)
    })
  }

  for _, f := range functions {
    f()
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run for_loop_refer_assign.go 
10
10
10
10
10
10
10
10
10
10  

按照我们在理解,不是应该按顺序输出“0”到“9”的数字吗?这是因为,当我们在“for”循环做迭代的时候,“for”内部的变量传递不是我们通常理解的值传递,而是引用传递,导致最后函数切片中所有的函数中使用到的变量“i”都是循环之后变量“i”的值,从而最后输出的都是“10”。

那如果我们想按照初衷一次打印每个迭代中的数字,如何做呢?

有一种办法可以做到,因为函数参数是值传递,可以通过一个辅助函数达到目的:

 package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    functions = append(functions, build(i))
  }

  for _, f := range functions {
    f()
  }
}

func build(val int) func() {
  return func() {
    fmt.Println(val)
  }
}  

运行结果:

 ➜  closure git:(master) ✗ go run for_loop_refer_assign.go 
0
1
2
3
4
5
6
7
8
9  

相关文章