函数与闭包
闭包就是指有权访问另一个函数作用域中的变量的函数。闭包由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
正常情况下,外部函数调用完后其内部变量对象就应该被销毁,但闭包的存在使得我们仍然能够访问外部函数的变量对象。
Go语言对函数式编程主要是体现在闭包上面。
函数式编程 vs 函数指针:
- 函数是一等公民:参数,变量,返回值都可以是函数(c++里只有函数指针,Java里函数只是一个名字)
- 高阶函数:函数的参数可以是一个函数
- 局部变量:函数内变量,函数调用结束后释放
- 自由变量:可以在函数外被调用,是闭包产生的变量
闭包常用的表现形式是:在函数内返回另一个函数
package main
import "fmt"
// 返回一个新函数
func fib() func() int {
a,b := 0,1 // 声明闭包变量
return func() int {
b,a = a+b,b
return b
}
}
func main() {
f := fib()
for i := 0 ; i < 20;i++ {
fmt.Printf("%d ",f())
}
}
// 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946
其他语言对闭包的支持
Python 中的闭包: python原生支持闭包、使用_closure_
来查看闭包内容
# Python闭包
def adder():
sum = 0
def f(value):
nonlocal sum
sum += value
return sum
return f
C++ 中的闭包:
过去stl或者boost带有类似库;C++11及以后:支持闭包
以下是C++14下编译通过的
auto adder(){
auto sum = 0;
return [-] (int value) mutable {
sum += value;
return sum;
}
}
Java 中的闭包
-
1.8以前: 匿名类或Lambda表达式均支持闭包
-
1.8以后: 使用 Function 接口和 Lambda 表达式来创建函数对象 函数本身不能作为参数和返回值的
Function<Integer,Integer> adder() {
final Holder<Integer> sum = new Holder<>(0);
return (Integer value) -> {
sum.value += value;
return sum.value;
}
}
函数式编程应用
-
案例一: 斐波那契数列
// 斐波那契数列闭包函数 func Fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } } func main() { f := fib() for i := 0 ; i < 20;i++ { fmt.Printf("%d ",f()) } } // 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946
-
案例二:为函数实现接口,使用接口实现斐波那契数列结果打印
// 使用闭包返回 IntGen 类型 func fib() IntGen { a,b := 0,1 return func() int { b,a = a+b,b return b } } // 定义接口 type IntGen func() int // 接口方法 func (g IntGen) Read(p []byte) (n int,err error) { // 执行闭包函数,得到结果 next := g() // 添加限制条件 if next > 1000000 { return 0,io.EOF } // 打印信息 s := fmt.Sprintf("%d ",next) return strings.NewReader(s).Read(p) } // 实现成为一个read的接口 func PrintFib(read io.Reader) { scanner := bufio.NewScanner(read) for scanner.Scan() { // 使用 scanner.Scan(), 像读文件一样打印斐波那契数列。 fmt.Println(scanner.Text()) } } func main() { f := fib() PrintFib(f) }
-
案例三: 使用函数遍历二叉树
func (t *TreeNode) Traverse() { if t == nil { return } // 二叉树遍历 t.Left.Traverse() fmt.Println(t.Val) t.Right.Traverse() }
使用闭包,给二叉树遍历扩展新功能。
func (t *TreeNode) Traverse() { cnt := 0 // 使用闭包,给遍历的时候添加打印功能 t.TraverseFunc(func(node *TreeNode) { fmt.Printf("%d ",node.Val) cnt ++ }) // 打印出一共又多少节点 fmt.Println("\nNode cnt is",cnt) } func (t *TreeNode) TraverseFunc(f func (*TreeNode) ) { if t == nil { return } // 二叉树遍历 t.Left.TraverseFunc(f) f(t) t.Right.TraverseFunc(f) }
Go语言闭包的特点
- 更为自然,不需要修饰如何访问自由变量
- 没有 Lambda 表达式,但是又匿名函数