Go语言 一级函数 --- 匿名函数、高阶函数、闭包

文章目录

    • 导言
    • 一级函数
      • 一级函数是什么?
      • 匿名函数
      • 自定义函数类型
      • 高阶函数
        • 将 `x` 函数 作为 `y`函数 的参数
        • 从 `x`函数 中,返回 `y`函数
      • 闭包
      • 一级函数的实际用法
    • 原作者留言
    • 最后

导言

  • 原文链接: Part 33: First Class Functions
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

一级函数

一级函数是什么?

当一个语言,具有以下特性:

  1. 支持把函数赋值给变量
  2. 将函数以参数形式传递给其他函数
  3. 能作为其他函数的返回值

那我们就说这个语言支持一级函数。

Go语言 拥有上述的特性,所以它支持一级函数。

接下来,我们将讨论一级函数的句式,与其多样的用法。

匿名函数

我们来看一个简单的例子。在这个例子中,我们把一个函数赋给了一个变量。

package main

import (  
    "fmt"
)

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

在上面程序的第 8 行,我们把函数赋值给了变量。如果你仔细观察,你会发现:分配给 a 的函数,它是没名字的。这一类函数被叫做匿名函数,因为它们没用名字。

现在,我们只能通过 变量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")
    }()
}

在上面程序的第 8 行,有一个匿名函数被定义,且被立即调用。此时程序输出如下:

hello world first class function  

我们也可以向匿名函数传递参数。

package main

import (  
    "fmt"
)

func main() {  
    func(n string) {
        fmt.Println("Welcome", n)
    }("Gophers")
}

在上面程序的第 10 行,我们向匿名函数传递了一个 string类型 的参数。运行程序,输出如下:

Welcome Gophers 

自定义函数类型

与结构体类似,我们也可以自定义函数类型。

type add func(a int, b int) int 

上面的程序片段,创建了一个新的函数类型 add,这个函数类型接收 2 个整型参数,返回值类型也是整型。现在,我们就能定义 add类型 的变量了。

来写个程序吧,在这个程序中,我们定义了一个 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)
}

在上面程序的第 10 行,我们定义了一个类型为 add 的 变量a。之后,我们给它分配了一个函数 — 这个函数的签名符合 类型add。在第 13 行,我们调用了这个函数,并把其返回结果存储到 s 中。程序输出如下:

Sum 11

高阶函数

在维基百科中,高阶函数是这样定义的:只要一个函数拥有以下的特性之一,它就是一个高阶函数。
特性如下:

  1. 拥有一个或多个函数类型的参数。
  2. 将函数作为返回值。

让我们来看一下,关于上述两种情况的例子。

x 函数 作为 y函数 的参数

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)
}

在上面程序的第 7 行,我们定义了一个 simple函数,这个函数将函数作为参数 — 参数类型是 func(a, b int) int。在第 12 行的 main函数 中,我们创建了一个 匿名函数f,它符合 simple函数 参数的签名。在下一行,我们将 f 作为参数,传递给 simple函数,并对其进行调用。这个程序的输出将是:67

x函数 中,返回 y函数

现在,我们来重写上面的程序,使得 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))
}

在上面程序的第 7 行,simple函数返回了一个函数,该函数类型为 func(a, b int) int
在第 15 行,simple函数 被调用,它的返回值被赋给了 s。此时,s 就内含了simple函数 返回的函数。
在第 16 行,我们向 s 传递 2int型 参数,并进行调用。同样的,程序也会输出 67

闭包

闭包是匿名函数的特例。闭包就是一个匿名函数,只不过这个匿名函数访问了一些变量 — 这些变量位于匿名函数体之外。

用例子说明问题吧~

package main

import (  
    "fmt"
)

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

在上面程序的第 10 行,匿名函数访问了 变量a,而 变量a 位于匿名函数之外。因此,这个匿名函数就是一个闭包。

闭包与外部变量具有绑定关系。通过下面的例子,我们来理解这句话的意思。

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("!"))
}

在上面的程序中,函数appendStr 返回一个闭包。这个闭包被绑定到了 变量t。让我们理解一下这意味着什么。

声明在第 1718 行的变量 ab,它们都是闭包。它们都被绑定到了 自己的 变量t

在第 19行,我们向 a 传递 参数World 并调用它。此时,a闭包 的 t变量 将变为 Hello World

在第 20 行,我们向 b 传递 参数Everyone 并调用它。因为 b闭包 被绑定到了 自己的 变量t,所以初始时,b闭包 的 变量tHello。因此,在函数调用过后,b闭包 的 t 将变为 Hello Everyone。程序的余下部分不言自明。

这个程序的输出结果为:

Hello World  
Hello Everyone  
Hello World Gopher  
Hello Everyone ! 

一级函数的实际用法

到目前为止,我们定义了什么是一级函数,也通过一些人为示例,去理解一级函数是如何工作的。接下来,我们来写一个具体的程序,去展示一级函数的实际用法。

我们将创建一个程序,这个程序可以根据条件,过滤掉一些学生。让我们逐步解决这个问题吧~

首先,定义学生结构。

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

下一步就是写 filter函数 了。这个函数将会接收一些学生、一个函数 — 这个函数可以判断这些学生是否如何条件。编写了这个函数后,我们将会有更好的理解。现在就搞它~

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 的第 2 个参数是一个函数 — 该函数的类型为 func(student) bool,这个函数会判断,特定学生是否符合条件。
在第 3 行,我们遍历了整个 s — 类型是 []student,我们将它内部的每个学生,都作为参数,传递给 f函数。如果 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)
}

main函数 中,我们首先创建了两个学生 — s1s2,并把他们加入 切片s。现在,假如我们要找出等级为 B 的学生。
在上面的程序中,我们已经创建了这个函数 — 它是一个匿名函数,它会检查学生的等级是否为 B,如果是 B,则返回 true
在第 38 行,我们将这个匿名函数传递给 filter函数。
最终,程序会输出:

[{Samuel Johnson B USA}]

假设此时,我们要找出所有来自 India 的学生。这很简单,我们只需修改 filter函数的第 2 个参数 — 其实就是修改过滤条件。代码如下:

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)
}

程序输出如下:

[25 30 35 40 45]

来快速回顾下,本节学习了什么吧~

  1. 什么是一级函数?
  2. 匿名函数
  3. 自定义函数类型
  4. 高阶函数
    • 将函数作为参数,传递给另外一个函数
    • 在函数中,返回另外一个函数
  5. 闭包
  6. 一级函数的实际用法

这就是一级函数了,祝你暴富~

原作者留言

优质内容来之不易,您可以通过该 链接 为我捐赠。

最后

感谢原作者的优质内容。

这是我的第六次翻译,欢迎指出文中的任何错误。

你可能感兴趣的:(go语言,翻译)