一等公民函数
函数是一等公民
Golang中函数可以:
赋值给某个变量
作为入参传递给另外一个函数
作为其他函数的返回值
在编程语言中,我们一般说这种函数是类似于变量的一等公民,因为在其他编程语言中,一般只有变量才符合这些特性。
当然,除了Golang,Javascript中函数也是一等公民。将函数做为一等公民非常有意义,这类编程语言一般可以做为函数式编程语言使用。
匿名函数
我们先来看一个将函数赋值给变量的例子。
package main
import "fmt"
func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
}这里我们将一个函数赋值给了变量a。注意这个函数没有声明函数名称,这类函数一般称为匿名函数。
由于该匿名函数没有函数名称,因此我们只能通过变量a来调用。该程序执行结果如下:
hello world first class function
func()当然我们也可以通过立即调用来调用匿名函数。
package main
import "fmt"
func main() {
func() {
fmt.Println("hello world first class function")
}()
}这里我们定义了一个匿名函数并进行了立即调用,直接结果如下:
hello world first class function立即调用也可以传参数进去。
package main
import "fmt"
func main() {
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}该程序执行结果如下:
Welcome Gophers自定义函数类型
我们不仅可以定义结构体类型、接口类型,还可以自定义函数类型。
type add func(a int, b int) int这行代码定义了一个新的函数类型add,该类型的函数要求接收两个int参数作为入参,函数返回值类型也为int。下面我们定义一个该类型函数的变量。
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)
}这里我们定义了一个add类型的变量a,我们给该变量赋的值是一个入参为两个int,返回值也为int的函数。注意我们赋值的函数签名必须符合add类型的定义。该程序执行结果如下:
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)
}这里我们定义了函数simple,该函数的入参是函数类型。我们在main函数内部创建了一个符合simple入参要求的匿名函数,并传递给simple函数来调用。程序输出为67。
返回一个新函数
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))
}这里定义的函数simple返回了一个新函数f,f入参为两个int,返回值类型也是int。我们在main函数中调用simple(),赋值给s。因此s的值即为函数f。程序输出为67。
闭包
将函数作为一等公民的编程语言一般都有闭包的概念,比如Javascript,Golang也不例外。闭包是指在函数内部可以访问到函数外部定义的变量。当然,如果函数嵌套级别较深,可能会有多层访问,也就形成了作用域链。 听起来有点晦涩,我们通过例子来理解下。
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()
fmt.Println(a("World"))
fmt.Println(a("Gopher"))
}我们定义了一个函数appendStr,该函数的返回值是一个新函数,因此该函数是一个高阶函数。在appendStr函数内部,我们定义了变量t,因此在被返回的函数c内部我们是能够访问到t这个变量的,也就是说t在函数c的作用域链上。
我们在main函数中调用appendStr()赋值给变量a,这时a的值即为c,是appendStr返回的函数。由于t变量在函数c的作用域链上,因此每次调用a(也就是调用c)时都能访问到变量t,也就是说可以多次调用a来更新变量t的值。因此第一次调用a时t由Hello更新成了Hello World,第二次调用a时,t的值已经时Hello World了。
该程序输出如下:
Hello World
Hello World Gopher这里提到的作用域链非常重要,原文没有提到这个,我个人觉得重要就补充了下,是学习高阶函数和闭包不容易理解的地方,是难点也是核心。
下面我们升级下前面的程序,来深刻体会下作用域链。
package main
import "fmt"
var g = "Hi"
func appendStr() func(string) string {
t := "Hello"
g = g + " " + "Hi"
c := func(b string) string {
t = t + " " + b + g
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外部定义了一个变量g,因此g位于函数appendStr的作用域链上。调用appendStr可以访问到其作用域链上的g,第一次调用appendStr后g更新为Hi Hi,第二次调用后g更新为Hi Hi Hi。该程序输出为:
Hello WorldHi Hi Hi
Hello EveryoneHi Hi Hi
Hello WorldHi Hi Hi GopherHi Hi Hi
Hello EveryoneHi Hi Hi !Hi Hi Hi真实适用场景
前面我们看了几个例子,下面我们看下作为一等公民的函数都有哪些真实适用场景。 我来实现实现一个函数filter来根据某些条件过滤一个切片,这在函数式编程中是非常常见的一种用法。
首先我们定义几个结构体类型student。
type student struct {
firstName string
lastName string
grade string
country string
}下面实现函数filter,入参为一个切片列表,一个过滤条件判断函数。
func filter(s []student, f func(student) bool) []student {
var s []student
for _, v := range s {
if f(v) {
r = append(r, v)
}
}
}下面是完成程序:
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) {
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 {
return s.grade == "B"
})
fmt.Println(f)
}我们定义了两个student结构体变量,然后通过filter函数过滤出了grade为B的student。输出为:[{Samuel Johnson B USA}]。
假设我们现在像过滤出来自India,即印度的同学,那么只需要把过滤判断函数改为如下即可:
f := filter(s, func(s student) bool {
return s.country == "India"
})
fmt.Println(f)是不是很方便好用?
下面我们再实现一个函数iMap,用来遍历处理一个int类型的切片,并返回处理后的切片。
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)
}这里我们将切片中的每个元素都乘了5,输出为:[25 30 35 40 45]。
像filter、map这些函数都是借助高阶函数这个特性来实现的,其实本质上是属于函数式编程的范畴,感兴趣的话可以接着去深入学习函数式编程,Haskell、Golang、Javascript、Python等编程语言都支持函数式编程。学习函数式编程对编程水平的提升绝对大有裨益。
Last updated
Was this helpful?