Go语言开发(六)、Go语言闭包
一、函数式编程
1、函数式编程简介
函数式编程是一种编程模型,将计算机运算看作是数学中函数的计算,并且避免了状态以及变量的概念。
在面向对象思想产生前,函数式编程已经有数十年的历史。随着硬件性能的提升以及编译技术和虚拟机技术的改进,一些曾被性能问题所限制的动态语言开始受到关注,Python、Ruby和Lua等语言都开始在应用中崭露头角。动态语言因其方便快捷的开发方式成为很多人喜爱的编程语言,伴随动态语言的流行,函数式编程也开始流行。
2、函数式编程的特点
函数式编程的主要特点如下:
A、变量的不可变性: 变量一经赋值不可改变。如果需要改变,则必须复制出去,然后修改。
B、函数是一等公民: 函数也是变量,可以作为参数、返回值等在程序中进行传递。
C、尾递归:如果递归很深的话,堆栈可能会爆掉,并导致性能大幅度下降。而尾递归优化技术(需要编译器支持)可以在每次递归时重用stack。
3、高阶函数
在函数式编程中,函数需要作为参数传递,即高阶函数。在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
A、函数可以作为参数被传递
B、函数可以作为返回值输出
二、匿名函数
1、匿名函数简介
匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成。C和C++不支持匿名函数。
func(x,y int) int {
return x + y
}
2、匿名函数的值类型
在Go语言中,所有的函数是值类型,即可以作为参数传递,又可以作为返回值传递。
匿名函数可以赋值给一个变量:
f := func() int {
...
}
定义一种函数类型:type CalcFunc func(x, y int) int
函数可以作为值传递:
func AddFunc(x, y int) int {
return x + y
}
func SubFunc(x, y int) int {
return x - y
}
...
func OperationFunc(x, y int, calcFunc CalcFunc) int {
return calcFunc(x, y)
}
func main() {
sum := OperationFunc(1, 2, AddFunc)
difference := OperationFunc(1, 2, SubFunc)
...
}
函数可以作为返回值:
// 第一种写法
func add(x, y int) func() int {
f := func() int {
return x + y
}
return f
}
// 第二种写法
func add(x, y int) func() int {
return func() int {
return x + y
}
}
当函数返回多个匿名函数时建议采用第一种写法:
func calc(x, y int) (func(int), func()) {
f1 := func(z int) int {
return (x + y) * z / 2
}
f2 := func() int {
return 2 * (x + y)
}
return f1, f2
}
匿名函数的调用有两种方法:
// 通过返回值调用
func main() {
f1, f2 := calc(2, 3)
n1 := f1(10)
n2 := f1(20)
n3 := f2()
fmt.Println("n1, n2, n3:", n1, n2, n3)
}
// 在匿名函数定义的同时进行调用:花括号后跟参数列表表示函数调用
func safeHandler() {
defer func() {
err := recover()
if err != nil {
fmt.Println("some exception has happend:", err)
}
}()
...
}
三、闭包
1、闭包的定义
函数可以嵌套定义(嵌套的函数一般为匿名函数),即在一个函数内部可以定义另一个函数。Go语言通过匿名函数支持闭包,C++不支持匿名函数,在C++11中通过Lambda表达式支持闭包。
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,函数代码在函数被定义后就确定,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。约束是指一个变量的名字和其所代表的对象之间的联系。由于在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境,因此需要将引用环境与函数组合起来。
2、闭包的本质
闭包是包含自由变量的代码块,变量不在代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。由于自由变量包含在代码块中,所以只要闭包还被使用,那么自由变量以及引用的对象就不会被释放,要执行的代码为自由变量提供绑定的计算环境。
闭包可以作为函数对象或者匿名函数。支持闭包的多数语言都将函数作为第一级对象,即函数可以存储到变量中作为参数传递给其它函数,能够被函数动态创建和返回。
func add(n int) func(int) int {
sum := n
f := func(x int) int {
var i int = 2
sum += i * x
return sum
}
return f
}
add函数中函数变量为f,自由变量为sum,同时f为sum提供绑定的计算环境,sum和f组成的代码块就是闭包。add函数的返回值是一个闭包,而不仅仅是f函数的地址。在add闭包函数中,只有内部的匿名函数f才能访问局部变量i,而无法通过其它途径访问,因此闭包保证了i的安全性。
当分别用不同的参数(10, 20)注入add函数而得到不同的闭包函数变量时,得到的结果是隔离的,即每次调用add函数后都将生成并保存一个新的局部变量sum。
在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。
当每次调用add函数时都将返回一个新的闭包实例,不同实例之间是隔离的,分别包含调用时不同的引用环境现场。不同于函数,闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
从形式上看,匿名函数都是闭包。
函数只是一段可执行代码,编译后就固定,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数。
对象是附有行为的数据,而闭包是附有数据的行为。
3、闭包的使用
闭包经常用于回调函数,当IO操作(例如从网络获取数据、文件读写)完成的时候,会对获取的数据进行某些操作,操作可以交给函数对象处理。
除此之外,在一些公共的操作中经常会包含一些差异性的特殊操作,而差异性的操作可以用函数来进行封装。
package main
import "fmt"
func adder() func(int) int {
sum := 0
f := func(x int) int {
sum += x
return sum
}
return f
}
func main() {
sum := adder()
for i := 0; i < 10; i++ {
fmt.Println(sum(i))
}
}
四、闭包的应用
package main
import "fmt"
//普通闭包
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
//无状态、无变量的闭包
type iAdder func(int) (int, iAdder)
func adder2(base int) iAdder {
return func(v int) (int, iAdder) {
return base + v, adder2(base + v)
}
}
//使用闭包实现斐波那契数列
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
//普通闭包调用
a := adder()
for i := 0; i < 10; i++ {
var s int =a(i)
fmt.Printf("0 +...+ %d = %d\n",i, s)
}
//状态 无变量的闭包 调用
b := adder2(0)
for i := 0; i < 10; i++ {
var s int
s, b = b(i)
fmt.Printf("0 +...+ %d = %d\n",i, s)
}
//调用斐波那契数列生成
fib:=Fibonacci()
fmt.Println(fib(),fib(),fib(),fib(),fib(),fib(),fib(),fib())
}
有疑问加站长微信联系(非本文作者)