回调函数和闭包

当函数具备以下两种特性的时候,就可以称之为高阶函数(high order functions):

  1. 函数可以作为另一个函数的参数(典型用法是回调函数)
  2. 函数可以返回另一个函数,即让另一个函数作为这个函数的返回值(典型用法是闭包)

一般来说,附带的还具备一个特性:函数可以作为一个值赋值给变量。

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=5x=8等等。

所以,什么时候使用闭包?一般来说,可以将过程分成两部分或更多部分都进行工厂化的时候,就适合闭包(实际上,有些地方直接将闭包称呼为工厂函数)。第一个部分是可以给自由变量批量绑定不同的值,第二部分是多个闭包函数可以共享第一步绑定后的自由变量

Go基础系列:函数(2)——回调函数和闭包的更多相关文章

  1. php变量函数,回调函数

    一,变量可以直接传递函数 <?php function demo($num , $n )//$n是个函数 { for($i=0;$i<$num;++$i) { if($n($i)) { e ...

  2. C++中类成员函数作为回调函数

    注:与tr1::function对象结合使用,能获得更好的效果,详情见http://blog.csdn.net/this_capslock/article/details/38564719 回调函数是 ...

  3. C++中 线程函数为静态函数 及 类成员函数作为回调函数

    线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运行,静态函数的地址是不变的,并不在线程堆栈中static只 ...

  4. php自定义函数之回调函数

    回调函数,可以配合匿名函数和变量函数实现更加优美.复杂的一种函数结构.大理石平台价格 回调函数,就是在处理一个功能的时候,我让让这个功能自定义能力再强一些,我准许调用这个函数的时候,还可以传入一个函数 ...

  5. 【知识点】inline函数、回调函数、普通函数

    目录 一.inline内联函数 1.1 使用 1.2 编译器对 inline 函数处理步骤 1.3 优缺点 1.3.1 优点 1.3.2 慎用内联 1.3.3 不宜使用内联 1.4 虚函数(virtu ...

  6. windows API普通函数跟回调函数有何区别

    通俗点讲:1.普通函数(假设我们都是函数)你卖电脑,我买电脑,我给你钱(调用你)后,你给我电脑(得到返回值).这种情况下,我给钱后就不能走开,必须等你把电脑给我,否则你交货的时候可能找不到人.2.回调 ...

  7. Python技法3:匿名函数、回调函数和高阶函数

    1.定义匿名或内联函数 如果我们想提供一个短小的回调函数供sort()这样的函数用,但不想用def这样的语句编写一个单行的函数,我们可以借助lambda表达式来编写"内联"式的函数 ...

  8. 使用匿名函数在回调函数中正确访问JS循环变量

    有时候, 需要以不同的参数调用某个URL,并且在回调函数中仍然可以访问正在使用的参数, 这时候, 需要使用闭包保存当前参数, 否则, 当回调函数执行时, 之前的参数很可能早已被修改为最后一个参数了. ...

  9. 上传图片的回调函数,callback作为一个函数针对回调函数

    Tool.ImageUpload = function (selector, callback) { /// <summary>图片上传</summary> /// <p ...

随机推荐

  1. Book : <Hands-on ML with Sklearn & TF> pdf/epub

    非常好的书,最近发现了pdf版本,链接:http://www.finelybook.com/hands-on-machine-learning-with-scikit-learn-and-tensor ...

  2. 8-unittest中case管理

    1.关联 在接口测试中难免碰到接口B的参数值来源于接口A的返回结果,此现象即为关联.在unittest中怎么处理这种情况呢?此问题通过全局变量来解决,将变量定义为全局变量:globals()[‘var ...

  3. html5 css选择器。 井号,句号的区别

    .理解CSS的样式组成CSS里的样式表是有规则组成的,每条规则有三个部分组成:1.选择器(如下面例子中的:“body”),告诉浏览器文档的哪个部分受规则影响:2.属性(如实例中的font-family ...

  4. MVC详解

    模型-视图-控制器(Modal View Controler,MVC)是Xerox PARC在八十年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已被广泛使用.最近几年被推荐为Sun ...

  5. 从 Spring Cloud 看一个微服务框架的「五脏六腑」

    原文:https://webfe.kujiale.com/spring-could-heart/ Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构 ...

  6. 315道python面试题(参考答案)

    第一部分 Python基础篇 1:为什么学习Python 家里有在这个IT圈子里面,也想让我接触这个圈子,然后给我建议学的Python, 然后自己通过百度和向有学过Python的同学了解了Python ...

  7. idea src下源文件和class编译文件不一致

    今天遇到一个神奇BUG,一个和elasticsearch没有任何关系的项目,报错ES某个包找不到,刚开始以为是依赖了父项目的某个包,并且本项目主启动类ComponentScan扫描了相关的类进入Spr ...

  8. 利用 Linux tap/tun 虚拟设备写一个 ICMP echo 程序

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 前面两篇文章已 ...

  9. Spring Boot到底是怎么运行的,你知道吗?

    导读 Spring Boot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较流行的微服务框架Spring ...

  10. html标签详解(1)

     http标签详解及讲解        1.基础标签 <!DOCTYPE html> <!--表示文本类型--> <html> <!--<html> ...