只需一文!深入理解闭包的实现

1 简介

Go 语言提供了一个称为匿名函数的特殊功能。匿名函数可以形成一个闭包。闭包是一种特殊类型的匿名函数,它引用在函数本身之外声明的变量。它类似于访问在函数声明之前可用的全局变量。这意味着这些函数几乎可以在任何地方使用。

这将为函数创建一个新属性,以了解其周围环境。此属性称为 闭包closure 属性。

  • 什么是 Golang 中的闭包?

当 Go 中的匿名函数可以访问其周围环境时,就会发生闭包。然后它可以保持自己的独特状态。 然后,当我们创建函数的新实例时,之前实例的状态将是独立的。

2 变量的范围

匿名函数,嵌套函数 和 返回函数都可以访问局部块变量,如果不使用局部变量隔离数据,那么就使用全局变量(package var)。

csharp 复制代码
var x = 0
func increment() int {
    x++
    return x
}

func main() {
    fmt.Println(increment())
    fmt.Println(increment())
}

全局变量 x 未作为参数传递给匿名函数,但该函数可以访问它。

在这个例子中,有一个小问题,因为将在package中定义的任何其他函数都可以访问全局变量 x,并且它可以在不调用 increment 函数的情况下更改它。

而闭包还提供了另一个方面,即数据隔离,我们来了解它。

css 复制代码
  func main() {
      x := 42
      fmt.Println(x)
      {
          fmt.Println(x)
          y := "功劳属于在擂台上的人。"
          fmt.Println(y)
      }
      // fmt.Println(y) // outside scope of y
  }

可以看到,我们也可以直接在main函数使用匿名函数,如果要在外部函数使用 闭包内部的变量就会报错。

3 匿名函数和嵌套函数

  • 嵌套函数

当我们在函数中创建另一个函数时,这称之为嵌套函数,以下main函数中通过创建匿名函数访问main函数变量x。

可以直接在函数中创建内部函数并且立即调用它。如此在另一个函数中创建一个函数。这称为嵌套函数。例如

go 复制代码
	package main
	import "fmt"

	// outer function
	func Hello(name string) {

	  // inner function
	  var displayName = func() {
	    fmt.Println("Hi", name)
	  }
	  // call inner function
	  displayName()

	}

	func main() {

	  // call outer function
	  Hello("Frank")  // Hi Frank

	}

在该示例中,我们在函数Hello内部创建了一个匿名函数displayName = func(){...}, 在这里displayName它是一个嵌套函数。嵌套函数displayName的工作方式与普通函数类似。它在函数Hello内部。

这里 是一个嵌套函数。嵌套函数的工作方式与普通函数类似。它在函数 var displayName = func() {...}displayName()

4 返回匿名函数

同样的,我们可以设置匿名函数为外部函数的返回值,也就是可以创建一个返回匿名函数的函数

go 复制代码
	package main
	import "fmt"

	func Hello() func() {
	  return func() {
	    fmt.Println("Hi Frank")
	  }
	}

	func main() {
	  h1 := Hello()
	  h1()
	}

输出

markdown 复制代码
	Hi Frank

在上面的示例中,我们创建了函数 Hello()。

go 复制代码
	func Hello() func() {...}

此处,大括号之前表示该函数返回另一个函数。func()

此外,如果你查看这个函数的 return 语句,我们可以看到该函数正在返回一个函数。

从 main() 中,我们调用函数Hello()。

css 复制代码
	h1 := Hello()

在这里,返回的函数现在被分配给变量。因此,执行嵌套的匿名函数。

5 闭包实现自然数包装器

Go 闭包是一个嵌套函数,它允许我们在外部函数关闭后访问外部函数的变量。 其中的嵌套函数返回一个函数,这里的一个嵌套函数示例。嵌套函数的工作方式与普通函数类似

go 复制代码
func wrapper() func() int {
    x := 0
    return func() int {
        x++
        return x
    }
}

func main() {
    increment := wrapper()
    fmt.Println(increment())
    fmt.Println(increment())
}

在main函数调用完成运行之后,就创建了一个名为increment的函数,

css 复制代码
increment := wrapper()

wrapper返回的函数现在已分配给变量 increment,它返回的函数也会引用变量 x 从0开始打印自然数,

但 increment() 函数之外的其他代码都无法访问此变量。 这就是在函数调用之间保持数据持久性的方式,同时将数据与其他代码隔离开来。

此时wrapper()这个外部函数的执行已完成,因此应销毁该变量。 但是,当我们使用increment时仍然可以使x递增并输出。 我们能够访问外部wrapper函数的变量x。

因为嵌套函数现在充当一个闭包,即使在执行外部函数之后,也会在其范围内关闭外部作用域变量。

因此闭包帮助我们限制多个函数使用的变量范围如果没有闭包,则两个或多个 func 可以访问同一个变量,该变量需要为包变量。

6 闭包实现斐波那契数列输出

那么如何使用闭包打印斐波那契数列,同时隔离数据呢?

go 复制代码
package main
import "fmt"

func fib() func() int {
	a := 1
	f := 0
	// returns inner function
	return func() int {
		f, a = a, f+a
		return f
	}
}

func main() {

	// call the outer function
	fdd := fib()
	// call the inner function
	for i := 0; i < 10; i++ {
		fmt.Println("f :", fdd())
	}

	// call the outer function again
	fdd2 := fib()
	fmt.Println(fdd2())

}

此代码执行外部函数并返回 斐波那契数的闭包。 这就是为什么我们可以在完成外部函数fib后, 可以连续通过fdd 访问 f 的变量。 而再次调用外部fib函数后,有fdd2 := fib(),重新得到1

在上面的示例中,该fib()函数返回一个匿名函数,

go 复制代码
		return func() int {
			f, a = a, f+a
			return f
		}

该函数将计算原有的变量 a和f,每次调用该匿名函数都将a的值赋予f,并修改其函数内部的变量a,然后并返回f。 这表明闭包数据是彼此隔离的。

7 小结

由此可以看到, 每次我们调用闭包外部函数时都会返回一个新的闭包。 在这里,每个返回的闭包都是彼此独立的,并且一个闭包的更改不会影响另一个闭包。

这有助于我们在彼此隔离的情况下处理多个数据。比如让我们看前面这个斐波那契数列的例子。

在需要数据隔离的地方,该闭包属性被广泛使用。它提供的数据状态使它们在这方面提供了极大的帮助。 当我们想创建一个状态封装的函数时,我们使用闭包来实现。

相关推荐
chxii16 分钟前
2.2goweb解析http请求信息
go
Asthenia041220 分钟前
为什么说MVCC无法彻底解决幻读的问题?
后端
Asthenia041221 分钟前
面试官问我:三级缓存可以解决循环依赖的问题,那两级缓存可以解决Spring的循环依赖问题么?是不是无法解决代理对象的问题?
后端
Asthenia041222 分钟前
面试复盘:使用 perf top 和火焰图分析程序 CPU 占用率过高
后端
Asthenia041222 分钟前
面试复盘:varchar vs char 以及 InnoDB 表大小的性能分析
后端
Asthenia041223 分钟前
面试问题解析:InnoDB中NULL值是如何记录和存储的?
后端
Asthenia04121 小时前
面试官问我:TCP发送到IP存在但端口不存在的报文会发生什么?
后端
Asthenia04121 小时前
HTTP 相比 TCP 的好处是什么?
后端
Asthenia04121 小时前
MySQL count(*) 哪个存储引擎更快?为什么 MyISAM 更快?
后端
Asthenia04121 小时前
面试官问我:UDP发送到IP存在但端口不存在的报文会发生什么?
后端