学习目标
-
Golang中,函数是一等公民是啥意思
-
什么是匿名函数
-
什么是高阶函数
-
什么是闭包,闭包的用法、常见应用场景
函数是一等公民
关于一等公民(First-class citizen)看看维基百科的定义:
In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.
关于函数是一等公民,在维基百科也有定义:
In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures. Some programming language theorists require support for anonymous functions (function literals) as well.In languages with first-class functions, the names of functions do not have any special status; they are treated like ordinary variables with a function type. The term was coined by Christopher Strachey in the context of "functions as first-class citizens" in the mid-1960s.
在Golang中,函数是一种数据类型,函数可以赋值给变量、结构体成员、作为一个函数的入参或出参,所以是一等公民。
匿名函数
顾名思义,没有名字的函数。Golang中匿名函数的常见应用是defer关键字延迟一个函数的执行、go关键字开启新的goroutine执行函数,如下:
func main() {
defer func() {
fmt.Println("defer func...")
}()
go func() {
fmt.Println("go func...")
}()
<-time.After(time.Second)
}
匿名函数实现延迟计算(惰性计算)
匿名函数的一个重要用途是捕获外部变量实现延迟计算。示例:
func main() {
var err error
defer func() {
// 引用外部变量err。在main函数执行完后才计算err的值,即延迟计算
if err != nil {
fmt.Printf("occur err:%v\n", err)
}
}()
fmt.Println("run...")
err = errors.New("have a error")
return
}
在创建匿名函数时,若外部变量err是直接值传递,则执行匿名函数时err一定是nil;那有了捕获机制(即外部变量传递给函数是引用传递),匿名函数可以等到执行时才计算外部变量err的值。
核心是:
外部变量传递给函数是引用传递,而不是直接值传递。
外部变量传递给函数是引用传递
切记:
外部变量传递给函数是引用传递,而不是值传递。示例:
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
funcs := make([]func(), 0)
for _, i := range arr {
funcs = append(funcs, func() {
// 是引用传递,传递的是外部变量i的地址,而不是值传递
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
}
因为是引用传递,所以上述程序输出结果都是10。
高阶函数
满足下列条件之一的函数即为高阶函数:
-
函数的入参有函数
-
函数的返回结果(出参)有函数
函数的入参有函数
一个函数的入参函数相当于对外开放的钩子(即钩子函数),函数调用方可以传递不同的入参函数实现从而实现不同的细节处理。
函数的出参有函数
闭包
维基百科
维基百科定义:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量 (该函数内部绑定的符号) ,也要包括自由变量 (在函数外部定义但在函数内被引用) ,有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。
闭包可以用来在一个函数与一组“私有”变量之间建立关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所建立所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中。
经典闭包示例
斐波那契数列:
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
什么是闭包
具体理解:
根据上文斐波那契数列的例子可以这样理解闭包:
函数A的返回值是函数B,且函数B中引用了外部变量, 则调用函数A就会产生一个闭包。
函数A调用完后,被「返回值函数」引用的变量不会立马被回收。
抽象理解:
闭包 = 函数 + 该函数引用的外部上下文环境。
「外部上下文环境」具体理解就是「外部变量」
闭包自带一个执行上下文环境
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
如上函数每次调用返回的闭包函数都会自带一个独立的执行上下文环境,该上下文环境包含变量a和变量b(初始值分别是0和1),闭包函数每次执行都会改变其上下文环境中的变量a和变量b的值。
可以这么理解:闭包让一个函数有自己的私有全局变量。
总结
-
闭包 = 函数 + 该函数引用的外部上下文环境
-
闭包函数自带一个执行上下文环境
-
关键词:环境
函数式编程
待研究