文章目录
一、背景知识
1.声明
先解释一下这个词
“声明”就是把一种类型的变量和一个名字联系起来。
1.1标准声明
格式
var 变量名 变量类型
例子
var name string
1.2批量声明
例子
var (
a string
b int
c bool
d float32
)
1.3短变量声明
短变量声明只能在函数内部使用。
package main
import (
"fmt"
)
// 全局变量m
var m = 100
func main() {
n := 10
m := 200 // 此处声明局部变量m
fmt.Println(m, n)
}
Go语言中的变量必须先声明后,才能使用,并且
- 同一作用域内不支持重复声明
- Go语言的变量声明后必须使用。
2.匿名函数
首先函数是go语言的“一等公民”,这就意味着有很多功能,匿名函数就是一种。
匿名函数就是没有函数名的函数。
因为没有函数名,所以只能立即执行的方式使用或者通过赋值给某个变量(包括作为函数入参/返回值),然后通过变量调用。
匿名函数的定义格式如下:
func(参数)(返回值){
函数体
}
例子
package main
import (
"fmt"
)
var f = func(int) {
}
func main() {
//像普通的类型(整型、字符串等)一样进行赋值
f = func(i int) {
fmt.Println(i)
}
f(2)
//f可以被任何输入一个整型,无返回值的函数给赋。
//因此f可以看成是一个函数类型的变量。这样,可以动态的改变f的功能。
f = func(i int) {
fmt.Println(i * i * i)
}
f(2)
// 匿名函数作为函数入参
testFuncFunc(func(in int) {
fmt.Println(in)
})
// 函数作为返回值
f = inputFunc()
f(4)
//函数自执行
func(in int){
fmt.Println(in)
}(5)
}
func testFuncFunc(f func(int)) {
f(3)
}
func inputFunc() func(in int){
// 由于函数内部不能定义函数,只能定义匿名函数
return func(in int) {
fmt.Println(in)
}
}
/*
2
8
3
4
5
*/
二、闭包
2.1什么是闭包
Go中的闭包指:一个函数和与其相关的引用环境组合而成的实体。
说实话这句话真的难理解,其实改成下面这样就好理解了
闭包是匿名函数与匿名函数所引用环境的组合。
匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。这就类似于常规函数直接使用全局变量一样,个人理解为:匿名函数和它引用的变量以及环境,类似常规函数引用全局变量处于一个包的环境。
看下面的例子更好理解
package main
import "fmt"
func main() {
n := 0
f := func() int {
n += 1
return n
}
fmt.Printf("%p\n", f)//这是个地址,结果可能不一样
fmt.Println(f())
fmt.Println(f())
}
/*
输出:
0x10a4ae0
1
2
*/
在上述代码中,
n := 0
f := func() int {
n += 1
return n
}
就是一个闭包
其中
f := func() int {
n += 1
return n
}
是匿名函数
在匿名函数中引用的变量n就可以看作这个匿名函数引用的环境
再仔细分析一下上面的例子
类比于常规函数+全局变量+包。f不仅仅是存储了一个函数的返回值,它同时存储了一个闭包的状态。这里虽然两次调用f(),但是只初始化了一次,即环境都是一样的
再看一个闭包作为返回值的例子
package main
import "fmt"
func main() {
add := func(base int) func(int) int {
return func(i int) int {
return base + i
}
}
add5 := add(5)
fmt.Println("add5(10)=", add5(10))
fmt.Println("add5(15)=", add5(15))
}
/*
结果
add5(10)= 15
add5(15)= 20
*/
这个例子中的“匿名函数”指返回的函数,“引用环境”指其周围的环境,比如base变量等
add是一个闭包作坊(但是大家都说成add是一个闭包),根据入参返回(生产)一个闭包。这样add5就是使用5作为add的参数得到的一个闭包。
闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量add5中,直到add5被销毁,整个闭包也被销毁。
再看这个闭包例子,内部的匿名函数“捕获”了和它在同一作用于的其他常量和变量(base变量)。这里的base变量不能在栈上分配,因为在栈上分配在函数调用结束后就销毁了,内部的匿名函数就引用了一个失效的变量,这是不允许的,所以闭包的环境中引用的变量不能够在栈上分配。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。由闭包的实质含义,我们可以推论:闭包获取捕获变量相当于引用传递,而非值传递;对于闭包函数捕获的常量和变量,无论闭包何时何处被调用,闭包都可以使用这些常量和变量,而不用关心它们表面上的作用域。
看下面这个例子
package main
import (
"fmt"
)
func main() {
add := func(base int) func(int) int {
y := 1
fmt.Printf("base变量地址%p\n", &base)
fmt.Printf("y局部变量地址%p\n", &y)
fmt.Printf("y局部变量值%v\n", y)
return func(i int) int {
y++
fmt.Printf("闭包匿名函数内base变量地址%p\n", &base)
fmt.Printf("闭包匿名函数内base变量值%v\n", base)
fmt.Printf("闭包匿名函数内y局部变量地址%p\n", &y)
fmt.Printf(