回调函数和闭包
当函数具备以下两种特性的时候, 就可以称之为高阶函数 (high order functions):
函数可以作为另一个函数的参数 (典型用法是回调函数)
函数可以返回另一个函数, 即让另一个函数作为这个函数的返回值 (典型用法是闭包)
一般来说, 附带的还具备一个特性: 函数可以作为一个值赋值给变量.
- f := func(){
- ...
- }
- f()
由于 Go 中函数不能嵌套命名函数, 所以函数返回函数的时候, 只能返回匿名函数.
先简单介绍下高阶函数, 然后介绍闭包.
高阶函数示例
例如, 将函数作为另一个函数的参数:
- package main
- import "fmt"
- func added(msg string, a func(a, b int) int) {
- fmt.Println(msg, ":", a(33, 44))
- }
- func main() {
- // 函数内部不能嵌套命名函数
- // 所以 main() 中只能定义匿名函数
- f := func(a, b int) int {
- return a + b
- }
- added("a+b", f)
- }
以下示例是函数返回另一个函数:
- package main
- import "fmt"
- func added() func(a, b int) int {
- f := func(a, b int) int {
- return a + b
- }
- return f
- }
- func main() {
- m := added()
- fmt.Println(m(33, 44))
- }
回调函数 (sort.SliceStable)
将函数 B 作为另一个函数 A 的参数, 可以使得函数 A 的通用性更强, 可以随意定义函数 B, 只要满足规则, 函数 A 都可以去处理, 这比较适合于回调函数.
在 Go 的 sort 包中有一个很强大的 Slice 排序工具 SliceStable(), 它就是将排序函数作为参数的:
func SliceStable(slice interface{}, Less func(i, j int) bool)
这个函数是什么意思呢? 给定一个名为 slice 的 Slice 结构, 使用名为 Less 的函数去对这个 slice 排序. 这个 Less 函数的结构为 Less func(i, j int) bool, 其中 i 和 j 指定排序依据. Go 中已经内置好了排序的算法, 我们无需自己去定义排序算法, Go 会自动从 Slice 中每次取两个 i 和 j 索引对应的元素, 然后去回调排序函数 Less. 所以我们只需要传递升序还是降序, 根据什么排序就可以.
例如:
- package main
- import (
- "fmt"
- "sort"
- )
- func main() {
- s1 := []int{112, 22, 52, 32, 12}
- // 定义排序函数
- Less := func(i, j int) bool {
- // 降序排序
- return s1[i]> s1[j]
- // 升序排序: s1[i] <s1[j]
- }
- //
- sort.SliceStable(s1, Less)
- fmt.Println(s1)
- }
这里的排序函数就是回调函数. 每取一次 i 和 j 对应的元素, 就调用一次 Less 函数.
可以将排序函数直接写在 SliceStable() 的参数位置:
- sort.SliceStable(s1, func(i, j int) bool {
- return s1[i]> s1[j]
- })
还可以更强大更灵活. 例如, 按照字符大小顺序来比较, 而不是按照数值大小比较:
- package main
- import (
- "fmt"
- "sort"
- "strconv"
- )
- func main() {
- s1 := []int{112, 220, 52, 32, 42}
- sort.SliceStable(s1, func(i, j int) bool {
- // 将 i 和 j 对应的元素值转换成字符串
- bi := strconv.FormatInt(int64(s1[i]), 10)
- bj := strconv.FormatInt(int64(s1[j]), 10)
- // 按字符顺序降序排序
- return bi> bj
- })
- fmt.Println(s1)
- }
按照字符串长度来比较:
- package main
- import (
- "fmt"
- "sort"
- )
- func main() {
- s1 := []string{"hello","malong","gaoxiao"}
- sort.SliceStable(s1, func(i, j int) bool {
- // 按字节大小顺序降序排序
- return len(s1[i])> len(s1[j])
- })
- fmt.Println(s1)
- }
更严格地说是按字节数比较, 因为 len() 操作字符串时获取的是字节数而非字符数. 如果要按照字符数比较, 则使用如下代码:
- package main
- import (
- "fmt"
- "sort"
- )
- func main() {
- s1 := []string{"hello","世界","gaoxiao"}
- sort.SliceStable(s1, func(i, j int) bool {
- // 按字节大小顺序降序排序
- return len([]rune(s1[i]))> len([]rune(s1[j]))
- })
- fmt.Println(s1)
- }
闭包
函数 A 返回函数 B, 最典型的用法就是闭包 (closure).
关于闭包详细的定义, 见我的另一篇文章, 无比详细一文搞懂: 词法作用域, 动态作用域, 回调函数, 闭包
简单地说, 闭包就是 "一个函数 + 一个作用域环境" 组成的特殊函数. 这个函数可以访问不是它自己内部的变量, 也就是这个变量在其它作用域内, 且这个变量是未赋值的, 而是等待我们去赋值的.
例如:
- package main
- import "fmt"
- func f(x int) func(int) int{
- g := func(y int) int{
- return x+y
- }
- // 返回闭包
- return g
- }
- func main() {
- // 将函数的返回结果 "闭包" 赋值给变量 a
- a := f(3)
- // 调用存储在变量中的闭包函数
- res := a(5)
- fmt.Println(res)
- // 可以直接调用闭包
- // 因为闭包没有赋值给变量, 所以它称为匿名闭包
- fmt.Println(f(3)(5))
- }
上面的 f() 返回的 g 之所以称为闭包函数, 是因为它是一个函数, 且引用了不在它自己范围内的变量 x, 这个变量 x 是 g 所在作用域环境内的变量, 因为 x 是未知, 未赋值的自由变量.
如果 x 在传递给 g 之前是已经赋值的, 那么闭包函数就不应该称为闭包, 因为这样的闭包已经失去意义了.
下面这个 g 也是闭包函数, 但这个闭包函数是自定义的, 而不是通过函数返回函数得到的.
- package main
- import "fmt"
- func main() {
- // 自由变量 x
- var x int
- // 闭包函数 g
- g := func(i int) int {
- return x + i
- }
- x = 5
- // 调用闭包函数
- fmt.Println(g(5))
- x = 10
- // 调用闭包函数
- fmt.Println(g(3))
- }
之所以这里的 g 也是闭包函数, 是因为 g 中访问了不属于自己的变量 x, 而这个变量在闭包函数定义时是未绑定值的, 也就是自由变量.
闭包的作用很明显, 在第一个闭包例子中, f(3) 退出后, 它返回的闭包函数 g() 仍然记住了原本属于 f() 中的 x=3. 这样就可以让很多闭包函数共享同一个自由变量 x 的值.
例如, 下面的 a(3),a(5),a(8) 都共享来自 f() 的 x=3.
- a := f(3)
- a(3)
- a(5)
- a(8)
再往外层函数看, f(3) 可以将自由变量 x 绑定为 x=3, 自然也可以绑定为 x=5,x=8 等等.
所以, 什么时候使用闭包? 一般来说, 可以将过程分成两部分或更多部分都进行工厂化的时候, 就适合闭包. 第一个部分是可以给自由变量批量绑定不同的值, 第二部分是多个闭包函数可以共享第一步绑定后的自由变量.
来源: https://www.cnblogs.com/f-ck-need-u/p/9878898.html