函数式编程(20)"/>
GO学习笔记——函数式编程(20)
在C中,我们有函数指针,函数指针可以作为参数传递给一个函数。
但是,在GO中,支持函数式编程,也就是说,它支持下面这个概念
头等函数:可以把函数赋值给变量,也可以把函数作为其他函数的返回值或者参数
所谓的函数是一等公民,也就是这么个意思。
匿名函数
所谓的匿名函数,就是没有名字的函数。这和我们平时所了解的可能不一样,为什么一个函数可以没有名字?但这在函数式编程中,确实是可行的,因为我们可以将一个没有名字的函数赋值给一个变量使用。
func main() {a := func(){fmt.Println("Hello World!")}a()fmt.Printf("%T\n",a)
}
我们将一个匿名函数赋值给变量a,调用该函数的唯一方法就是变量a,a()调用了该函数,另外我们还打印了变量a的类型。
执行结果
Hello World!
func()
a的类型是一个func(),也就是说,变量a是一个函数。
使用匿名函数也可以不用一个变量来接收。
func main() {func(str string){fmt.Println(str)}("Hello World!")
}
可以在函数后面直接跟一个()来调用,并且和普通函数一样,在()内可以给这个匿名函数传参数
执行结果
Hello World!
高阶函数
- 返回值是一个函数
- 有一个或多个参数是函数
满足上面任意一个条件的函数就是高阶函数。
//定义一个sum函数,它的参数是一个func,返回值也是一个func
func sum(a func(a,b int) int) func(){result := a(1,2)return func() {fmt.Println(result)}
}func main() {a := func(a,b int) int {return a + b}sum(a)() //sum(a)的返回值是一个函数,需要再用一个()来调用它
}
上面的sum函数,它的返回值是一个函数,它的参数也是一个函数,所以它是一个高阶函数。
注意,因为sum(a)调用完以后,得到的返回值也是一个函数,所以还需要再用一个()才可以得到我们想要的输出结果
执行结果
3
闭包
当一个匿名函数所访问的变量是定义在函数体的外部时,这样的匿名函数就是一个闭包
其实上面那个例子的函数就是一个闭包,因为在匿名函数中访问了result变量,而result是定义在匿名函数体外部的变量。
上面那个例子不能很好地突出闭包的概念,这里再来举一个例子。
//定义一个appendStr函数,可以追加字符串
func appendStr() func(string) string {v := "Hello"f := func (b string) string {v = v + " " + breturn v}return f
}func main() {a,b := appendStr(),appendStr()fmt.Println(a("LeBron"))fmt.Println(b("Kobe"))fmt.Println(a("James"))fmt.Println(b("Bryant"))
}
先看下执行结果
Hello LeBron
Hello Kobe
Hello LeBron James
Hello Kobe Bryant
函数appendStr返回了一个闭包,因为返回的匿名函数中调用了函数体外的变量v,因此该匿名函数就是一个闭包。
每一个闭包都绑定了一个外围变量,在这里v就是外围变量,因此,a和b闭包分别绑定了一个外围变量“Hello”。
首先用LeBron调用了闭包a,这个时候a中的外围变量就变成了“Hello LeBron”,闭包b也是同样的道理。
接着再用James调用了闭包a,这个时候a中的外围变量已经是更新过了的“Hello LeBron”,所以输出的“Hello LeBron James”,闭包b同理
用一张图来看看
所以在函数返回值是一个闭包的时候,它返回的不仅仅是一个匿名函数,而是一个闭包,闭包包括了这个匿名函数,同时闭包还保存了属于这个闭包的外围变量,对这个外围变量的引用会一直流传下去到后面的每一次调用。
是不是感觉,返回闭包,保存外围变量的这种功能,对于求斐波那契数列很有用?我试着实现了一下。
func fibonacci() func(index int) int {arr := []int{1,1}f := func (index int) int {if index < 2 {return arr[index]}else{arr = append(arr, arr[index - 1] + arr[index - 2])return arr[index]}}return f
}func main() {a := fibonacci()fmt.Println(a(0))fmt.Println(a(1))fmt.Println(a(2))fmt.Println(a(3))fmt.Println(a(4))
}
执行结果
1
1
2
3
5
在每次调用过程中,如果需要更新闭包的外围切片变量arr,那么每次更新的结果就都会被保存起来,所以就得到了如上的预期结果。
函数式编程实例
首先我们定义两个结构体
type student struct {name stringclass int
}type studentgrade struct {studentMath intEnglish intChinese intHistory int
}
一个是student结构体,包括name和class,另一个是studentgrede结构体,组合了student结构体,以及另外学生的四门成绩。
接下来我们定义一个filter函数,该函数用来过滤掉不满足条件的学生
func filter(s []studentgrade, f func(studentgrade) bool) []student {var r []studentfor _, v := range s {if f(v) == true {r = append(r,v.student)}}return r
}
filter函数的第一个参数是一个studentgrade切片,第二个参数是一个函数,这个函数其实就是一个过滤条件,filter函数的返回值是满足条件的学生的切片。
在main函数中,我们只需要设定好过滤条件就可以根据不同的过滤条件,用一个filter函数来过滤。
func main() {s1 := student{"pigff",1}s2 := student{"Kobe",2}s3 := student{"James",3}sg1 := studentgrade{s1,98,85,79,90}sg2 := studentgrade{s2,80,65,82,89}sg3 := studentgrade{s3,60,91,85,88}sg := []studentgrade{sg1,sg2,sg3}//找出数学成绩大于80的学生f := filter(sg,func(s studentgrade) bool{if s.Math > 80 {return true}return false})fmt.Println(f)
}
如上main函数的过滤条件就是找出数学成绩大于80的学生
执行结果
[{pigff 1}]
或者说,我们换一个过滤条件,找出英语和历史成绩都大于80分的同学,这个时候我们只要改变过滤条件函数就可以了。
func main() {s1 := student{"pigff",1}s2 := student{"Kobe",2}s3 := student{"James",3}sg1 := studentgrade{s1,98,85,79,90}sg2 := studentgrade{s2,80,65,82,89}sg3 := studentgrade{s3,60,91,85,88}sg := []studentgrade{sg1,sg2,sg3}//找出英语和历史成绩都大于80分的同学f := filter(sg,func(s studentgrade) bool{if s.English > 80 && s.History > 80 {return true}return false})fmt.Println(f)
}
执行结果
[{pigff 1} {James 3}]
所以,这就是函数是编程带来的好处,只要满足一个函数模板,就可以给一个函数传多个功能不同的函数,完成不同的功能。
更多推荐
GO学习笔记——函数式编程(20)
发布评论