写点什么

深入理解 Go 语言的一等函数及其应用

作者:宇宙之一粟
  • 2022-10-16
    广东
  • 本文字数:3751 字

    阅读完需:约 1 分钟

深入理解 Go 语言的一等函数及其应用

什么是 Go 中的一等函数

任何一门编程语言都离不开函数,无论是命令式语言 C、多范式编程语言 C++,还是面向对象编程语言 Java、Ruby,动态语言 Python、JavaScript,函数这一语法元素都是当仁不让的核心。


Go 语言没有面向对象语言的语法,比如类 、继承、对象,但 Go 语言中最重要的部分就是支持一等函数。


在 Go 语言中,函数式唯一一种基于特定输入、实现特定任务并可反馈任务执行结果的代码块。本质上 Go 程序就是一组函数的集合。


什么是一等函数


一等函数允许将函数分配给变量(将函数通过变量进行传递),作为参数传递给其他函数,并从其他函数返回。

匿名函数

让我们从一个简单的例子开始,它将一个函数分配给一个变量。


package main
import( "fmt")
func main() { a := func() { fmt.Println("Learning first class Function") } a()
fmt.Printf("%T", a)}
复制代码


在上面的程序中,我们利用 a := func() 给变量 a 分配了一个函数,这是将一个函数赋值给一个变量的语法。


然后我们分配给 a 的函数并没有名字,这类函数就被称为匿名函数


调用这个函数的唯一方法就是使用变量 a,所以在后面使用 a() 来调用这个函数,这就会打印出 Learning first class Function


然后我们打印变量 a 的类型,这将打印出 func()


运行结果:


Learning first class Functionfunc()
复制代码


也可以调用匿名函数而不把它赋值给一个变量,让我们来看一下下面的例子是如何做到这一点的:


package main
import ( "fmt")
func main() { func() { fmt.Println("Learing first class Function") }()}
复制代码


在上面的程序中,在第 8 行定义了一个匿名函数。紧接着我们在第 10 行用 () 调用该函数。这个程序将输出:


Learing first class Function
复制代码


也可以像其他函数一样,向匿名函数传递参数:


package main
import ( "fmt")
func main() { func(n string) { fmt.Println("Welcome to", n) }("Gophers's World!")}
复制代码


我们在上面的代码中,向匿名函数中传入一个 n string 字符串参数,然后在调用时传入一个 "Gophers's World!" ,此时,运行程序将得到如下的结果:


Welcome to Gophers's World!
复制代码

用户自定义的函数类型

就像我们自定义结构体类型一样,在 Go 语言中也支持自定义函数类型:


type add func(a int, b int) int
复制代码


上面的代码片段创建了一个新的函数类型 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 }
sum := a(2022, 10) fmt.Println("a + b = ", sum)}
复制代码


上面的程序中,我们定义了一个 add 类型的变量,并给它分配了一个签名与 add 类型相符的函数,接着通过 sum := a(2022,10) 调用并将结果赋给 sum,运行程序后得到如下的结果:


a + b =  2032
复制代码

高阶函数

对高阶函数的定义是这个函数至少做到以下的某一项的功能:


  • 以一个或者多个函数作为参数

  • 返回一个函数作为其结果

将函数作为参数传递给其他函数

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)}
复制代码


我们定义一个函数 simple 函数,它接收两个 int 参数,并返回一个 int 参数,然后把匿名函数传给变量 f,然后把 f 作为参数传递给 simple 函数,最终这个程序将打印 67 输出:


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(2022, 60))}
复制代码


运行该程序,得到结果;


2082
复制代码

闭包

闭包是匿名函数的一种特殊情况。闭包是匿名函数,它访问定义在函数主体之外的变量。


代码如下:


package main
import ( "fmt")
func main() {
a := 2022
func() { fmt.Println("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 上,变量 ab 是闭包,被绑定到它们自己的值 t 上。


我们传递参数 Worlda,然后 a 的值变成了 Hello World


传递参数 Everyone 给 b,然后 b 的值变成了 Hello Everyone


Hello World  Hello Everyone  Hello World Gopher  Hello Everyone !  
复制代码


闭包通常也是支持嵌套和 defer 工作的方法。在下面的例子中,我们可以看到一个允许我们嵌套工作的函数闭包:


package main
import ( "fmt" "sort")
func main() { input := []string{"foo", "bar", "baz"} var result []string // closure callback func() { result = append(input, "abc") result = append(result, "def") sort.Sort(sort.StringSlice(result)) }() fmt.Println(result)}
复制代码


运行结果:


[abc bar baz def foo]
复制代码

一等函数的实际应用

到目前为止,我们已经定义了什么是第一类函数,我们也看到了一些精心设计的例子来学习它们是如何工作的。现在让我们来写一个具体的程序,展示第一类函数的实际用途。


我们将创建一个程序,根据一些标准来过滤一部分学生。让我们一步一步地去做。


首先让我们定义学生类型:


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 的第二个参数是一个函数,它以一个 student 为参数,返回一个 bool 。这个函数确定一个特定的学生是否符合某个标准。我们在第 3 行遍历学生切片。如果该函数返回真,则意味着该学生通过了过滤标准,并被添加到切片 r 中。


现在来看一个完整的程序:


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)}
复制代码


在主函数中,我们首先创建了两个学生 s1 和 s2,并将他们添加到片断 s 中。现在我们假设要找出所有成绩为 B 的学生,在上述程序中,我们通过传递一个检查学生是否为 B 级的函数,如果是,则返回 true。 上述程序将打印:


[{Samuel Johnson B USA}]
复制代码


比方说,我们想找到所有来自印度的学生。这可以通过改变过滤器函数的参数来轻松实现。如下:


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


让我们再写一个程序来结束本文。这个程序将对一个切片的每个元素进行同样的操作,并返回结果。


例如,如果我们想将一个切片中的所有整数乘以 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]
复制代码

总结

在本文中,介绍了什么是一等函数的概念和功能,匿名函数、用户自定义函数类型、高阶函数和闭包,最后给出了一等函数的实际应用例子,希望这篇文章对你有所帮助!

发布于: 2022-10-16阅读数: 17
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020-05-07 加入

🏆InfoQ写作平台-签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于外企开发,弹性不加班 热衷分享,执着于阅读写作,佛系不水文 同名公众号:《宇宙之一粟》

评论 (1 条评论)

发布
用户头像
很棒啊
2022-10-17 09:07 · 北京
回复
没有更多了
深入理解 Go 语言的一等函数及其应用_函数_宇宙之一粟_InfoQ写作社区