闭包
很多语言都支持闭包,到底什么是闭包,很多人可能理解的并不透彻
先引用一些解释
维基百科讲: 闭包(Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
百度百科 闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分
再看下闭包的几种写法
//在函数里定义一个匿名函数,并且调用它
function printStr() {
$func = function( $str ) {
echo $str;
};
$func( 'some string' );
}printStr();
//例二
//在函数中把匿名函数返回,并且调用它
function getPrintStrFunc() {
$func = function( $str ) {
echo $str;
};
return $func;
}
$printStrFunc = getPrintStrFunc();
$printStrFunc( 'some string' );
//例三
//把匿名函数当做参数传递,并且调用它
function callFunc( $func ) {
$func( 'some string' );
}
$printStrFunc = function( $str ) {
echo $str;
};
callFunc( $printStrFunc );
//也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉
callFunc( function( $str ) {
echo $str;
} );
例1
package main
import (
"fmt"
)
//匿名函数外的变量
func f1(n int) func() {
n++
return func() {
fmt.Println(n)
}
}
//匿名函数内的变量
func f2(n int) func() {
return func() {
n++
fmt.Println(n)
}
}
//传址
func f3(n *int) func() {
*n++
fmt.Println("n=",*n)
return func() {
*n++
fmt.Println(*n)
}
}
func f4(n int) func(m int) {
r:=1
return func(m int){
r += n+m
fmt.Println(r)
}
}
var a = 20
var b = 10
var c = 0
var d = 30
var e = 40
func main() {
func1:=f1(a) //n++ 执行20+1,仅执行一次,实例化(初始化)闭包,匿名函数部分暂不执行,匿名函数以外的代码执行
fmt.Println(func1) //0x48e3d0 在这儿已经定义了n=20 ,然后执行++ 操作,所以是21 。
func1() //21 仅对闭包的的匿名函数部分的调用,匿名函数可使用所处环境的变量,所以仍是21
func1() //21 仅对闭包的的匿名函数部分的调用,匿名函数可使用所处环境的变量,所以仍是21
func1() //21 仅对闭包的的匿名函数部分的调用,匿名函数可使用所处环境的变量,所以仍是21
func2:=f2(b) //仅执行一次,实例化(初始化)闭包,实例化(初始化)闭包,匿名函数部分暂不执行,匿名函数以外的代码执行
fmt.Println(func2) //0x48e340 这儿定义了n 为10
func2() //11 仅对匿名函数调用,执行n++, 即使本次调用完成,匿名函数中引用的变量不会被销毁,一直保持,(普通函数调用完一次会被重置)
func2() //12 仅对匿名函数调用,执行n++, 即使本次调用完成,匿名函数中引用的变量不会被销毁,(普通函数调用完一次会被重置)
func2() //13 仅对匿名函数调用,执行n++, 即使本次调用完成,匿名函数中引用的变量不会被销毁,(普通函数调用完一次会被重置)
func3 := f3(&c) //仅执行一次,实例化(初始化)闭包,实例化(初始化)闭包,匿名函数部分暂不执行,匿名函数以外的代码执行
func3() //2 仅对匿名函数调用, 即使本次调用完成,匿名函数中引用的变量不会被销毁,一直保持,(普通函数调用完一次会被重置)
func3() //3 仅对匿名函数调用, 即使本次调用完成,匿名函数中引用的变量不会被销毁,一直保持,(普通函数调用完一次会被重置)
func3() //4 仅对匿名函数调用, 即使本次调用完成,匿名函数中引用的变量不会被销毁,一直保持,(普通函数调用完一次会被重置)
func4 := f4(d) //仅执行一次,实例化(初始化)闭包,实例化(初始化)闭包,匿名函数部分暂不执行,匿名函数以外的代码执行
func4(e) //71
func4(e) //141
func5 := f4(d) //实例化一个新的闭包,和其他实例的闭包没有相互关系
func5(e) //71
fmt.Println(a,b,c,d,e) //20,10,4,30,40 从结果可以看出,func1,func2,func4对引用环境中的原变量不产生影响,func3改变了引用环境的原变量
}
* 注意: 通过这个例子可以清晰看到,闭包内存保持的变量在匿名函数中,被外部传递的变量在这两种情况都不受影响
- for 循环中的闭包
func main() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second * 1)
}
// 输出 c c c, 闭包在子协程中执行,输出结果取决于子协程当时引用的v值
func main() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func() {
fmt.Println(v)
}()
time.Sleep(time.Second * 3)
}
fmt.Println("main routine")
time.Sleep(time.Second * 1) // 阻塞模式
}
//输出a, b, c , main routine, for中加了sleep, 子协程有机会在当前循环结束前执行
从以上抽取出特点
- 闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。
- 闭包的三种写法可以看出 ,在一个函数里执行一个匿名函数(1.在函数中执行,2.返回匿名函数执行,3.当参数传递后再调用执行)
- 可以使用当前声明处可见的所有变量
- 可以存在多个实例,将这个匿名函数变量赋值给其他的变量,将产生新的实例
- 在同一变量实例中,这个实例持续存在,即使一次调用结束,在下次调用仍然继承上次调用的结果
- 同一个实例,内存共享,多个实例相互不受影响,
- 一般函数在执行完一瞬间会销毁其执行环境.匿名函数用完即销毁,而闭包一次调用执行完, 闭包会一直存在内存中,垃圾收集器不会销毁闭包占用的内存。