深入理解 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 Function
func()
也可以调用匿名函数而不把它赋值给一个变量,让我们来看一下下面的例子是如何做到这一点的:
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
上,变量 a
和 b
是闭包,被绑定到它们自己的值 t
上。
我们传递参数 World
给 a
,然后 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]
总结
在本文中,介绍了什么是一等函数的概念和功能,匿名函数、用户自定义函数类型、高阶函数和闭包,最后给出了一等函数的实际应用例子,希望这篇文章对你有所帮助!
- 点赞
- 收藏
- 关注作者
评论(0)