golang教程33节-头等函数(first class functions)

原文来自:https://golangbot.com/first-class-functions/

什么是头等函数(first class functions)

编程语言支持头等函数是指:允许函数作为类型可以赋值给变量,作为参数传递给其他函数,从其他函数返回。Go语言支持头等函数。

这一节我们将导论头等函数的语法和各种使用案例。

匿名函数

让我们一个简单例子开始:将函数赋值给一个变量。

package main

import (  
    "fmt"
)

func main() {  
    a := func() {
        fmt.Println("hello world first class function")
    }
    a()
    fmt.Printf("%T", a)
}

在playground上运行

在上述代码中,我们将一个函数赋值给变量a(第8行)。只就是将函数赋值给变量的语法。仔细观察的话可以发现,赋值给变量的函数没有名字。这种没有名字的函数,我们称之为匿名函数(anonymous functions).

唯一调用这个函数的方法是使用变量a。我们通过a()来调用这个函数,函数打印hello world first class function。在第12行,我们打印变量a的类型。结果打印func()

程序执行结果:

hello world first class function  
func()  

不用赋值给变量的也可以调用匿名函数的。让我们来看看这个是如何实现的:

​
package main

import (  
    "fmt"
)

func main() {  
    func() {
        fmt.Println("hello world first class function")
    }()
}

​

在playground上运行

在上述代码中,一个字符创作为参数传递给匿名函数(第10行),程序执行结果:

Welcome Gophers  

用户自定义函数类型

就像我么你自定义结构体类型一样,我们也可以自定义函数类型。

type add func(a int, b int) int  

上述代码片段新建一个函数类型add:接收2个整型参数并返回一个整型。现在我们可以定义add类型变量。

看看下面这个例子:

package main

import (  
    "fmt"
)

type add func(a int, b int) int

func main() {  
    var a add = func(a int, b int) int {
        return a + b
    }
    s := a(5, 6)
    fmt.Println("Sum", s)
}

在playground上运行

在上述代码中,第10行,我们定义了一个自定义add类型的变量a,并将一个函数签名符合add类型的函数赋值给变量a。程序在第13行调用这个函数,返回值赋值给变量s。程序执行结果:

Sum 11  

高阶函数

高阶函数必须具备以下条件之一:

  • 拥有一个或多个参数
  • 函数作为返回值

针对上述两种情况,我们看看一些简单实例。

把函数当做参数传递给其他函数

package main

import (  
    "fmt"
)

func simple(a func(a, b int) int) {  
    fmt.Println(a(60, 7))
}

func main() {  
    f := func(a, b int) int {
        return a + b
    }
    simple(f)
}

在playground上执行

上述例子,我们定义了函数simple(第7行),它的参数是一个带有2个整型参数,并且返回值为整型的函数变量。在主函数中第12行,我们新建一个匿名函数,该函数的签名符合simple的函数签名。接下来给simple函数传递这个函数变量f并调用执行。程序执行结果是67.

在其它函数中返回一个函数

下面我们重写上述代码,实现从simple函数返回一个函数:

package main

import (  
    "fmt"
)

func simple() func(a, b int) int {  
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {  
    s := simple()
    fmt.Println(s(60, 7))
}

在playground上执行

上述程序,simple函数返回一个函数签名为2个整型参数和返回值为整型的函数。

simple函数在主函数15行调用。函数返回值赋值给变量s。现在s拥有从simple函数返回的函数。给s传递两个整型参数并调用,程序执行结果为67.

闭包

闭包是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。

看个示例更好理解:

package main

import (  
    "fmt"
)

func main() {  
    a := 5
    func() {
        fmt.Println("a =", a)
    }()
}

在playground上运行

上述代码中,匿名函数访问的变量a是在函数体外定义的(在第10)。因此该匿名函数是一个闭包。

每一个闭包都会绑定一个它自己的外围变量(Surrounding Variable)。我们通过一个简单示例来体会这句话的含义。

package main

import (  
    "fmt"
)

func appendStr() func(string) string {  
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {  
    a := appendStr()
    b := appendStr()
    fmt.Println(a("World"))
    fmt.Println(b("Everyone"))

    fmt.Println(a("Gopher"))
    fmt.Println(b("!"))
}

在playground上运行

在上述程序中,函数appendStr返回一个闭包。这个闭包的外围变量是t。我们来理解这是什么意思。

在第 17 行和第 18 行声明的变量 a 和 b都是闭包,它们绑定了各自的 t值。

我们首先用参数 world 调用了a。现在 a 中 b值变为了 Hello World。

在第 20 行,我们又用参数 Everyone调用了 b。由于 b 绑定了自己的变量 t,因此 b中的 t还是等于初始值 Hello。于是该函数调用之后,b中的t变为了 Hello Everyone。程序的其他部分很简单,不再解释。

该程序会输出:

Hello World  
Hello Everyone  
Hello World Gopher  
Hello Everyone !

头等函数的应用

迄今为止,我们已经定义了什么是头等函数,也看了一些专门设计的示例,来学习它们如何工作。现在我们来编写一些实际的程序,来展现头等函数的实际用处。

我们会创建一个程序,基于一些条件,来过滤一个 students 切片。现在我们来逐步实现它。

首先定义一个student类型。

type student struct {  
    firstName string
    lastName string
    grade string
    country string
}

下一步是编写一个 filter 函数。该函数接收一个 students 切片和一个函数作为参数,这个函数会计算一个学生是否满足筛选条件。写出这个函数后,你很快就会明白,我们继续吧。

func filter(s []student, f func(student) bool) []student {  
    var r []student
    for _, v := range s {
        if f(v) == true {
            r = append(r, v)
        }
    }
    return r
}

在上面的函数中,filter 的第二个参数是一个函数。这个函数接收 student 参数,返回一个 bool 值。这个函数计算了某一学生是否满足筛选条件。我们在第 3 行遍历了 student 切片,将每个学生作为参数传递给了函数 f。如果该函数返回 true,就表示该学生通过了筛选条件,接着将该学生添加到了结果切片 r中。你可能会很困惑这个函数的实际用途,等我们完成程序你就知道了。我添加了 main 函数,整个程序如下所示:

package main

import (  
    "fmt"
)

type student struct {  
    firstName string
    lastName  string
    grade     string
    country   string
}

func filter(s []student, f func(student) bool) []student {  
    var r []student
    for _, v := range s {
        if f(v) == true {
            r = append(r, v)
        }
    }
    return r
}

func main() {  
    s1 := student{
        firstName: "Naveen",
        lastName:  "Ramanathan",
        grade:     "A",
        country:   "India",
    }
    s2 := student{
        firstName: "Samuel",
        lastName:  "Johnson",
        grade:     "B",
        country:   "USA",
    }
    s := []student{s1, s2}
    f := filter(s, func(s student) bool {
        if s.grade == "B" {
            return true
        }
        return false
    })
    fmt.Println(f)
}

在playground上运行

在 main 函数中,我们首先创建了两个学生 s1 和 s2,并将他们添加到了切片 s。现在假设我们想要查询所有成绩为 B 的学生。为了实现这样的功能,我们传递了一个检查学生成绩是否为 B 的函数,如果是,该函数会返回 true。我们把这个函数作为参数传递给了 filter 函数(第 38 行,匿名函数作为参数)。上述程序会输出:

[{Samuel Johnson B USA}]

假设我们想要查找所有来自印度的学生。通过修改传递给 filter 的函数参数,就很容易地实现了。

实现它的代码如下所示:

c := filter(s, func(s student) bool {  
    if s.country == "India" {
        return true
    }
    return false
})
fmt.Println(c)

请将该函数添加到 main 函数,并检查它的输出。

我们最后再编写一个程序,来结束这一节的讨论。这个程序会对切片的每个元素执行相同的操作,并返回结果。例如,如果我们希望将切片中的所有整数乘以 5,并返回出结果,那么通过头等函数可以很轻松地实现。我们把这种对集合中的每个元素进行操作的函数称为 map 函数。相关代码如下所示,它们很容易看懂。

package main

import (  
    "fmt"
)

func iMap(s []int, f func(int) int) []int {  
    var r []int
    for _, v := range s {
        r = append(r, f(v))
    }
    return r
}
func main() {  
    a := []int{5, 6, 7, 8, 9}
    r := iMap(a, func(n int) int {
        return n * 5
    })
    fmt.Println(r)
}

在 playground 上运行

该程序会输出:

[25 30 35 40 45]

现在简单概括一下本教程讨论的内容:

  • 什么是头等函数?
  • 匿名函数
  • 用户自定义的函数类型
  • 高阶函数
    • 把函数作为参数,传递给其它函数
    • 在其它函数中返回函数
  • 闭包
  • 头等函数的实际用途

本节课程到此结束。祝您愉快。

ps:新手翻译,欢迎大家指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值