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 + " " + b
		return 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 string
	class int
}

type studentgrade struct {
	student
	Math int
	English int
	Chinese int
	History int
}

一个是student结构体,包括name和class,另一个是studentgrede结构体,组合了student结构体,以及另外学生的四门成绩。

接下来我们定义一个filter函数,该函数用来过滤掉不满足条件的学生

func filter(s []studentgrade, f func(studentgrade) bool) []student {
	var r []student
	for _, 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}]

所以,这就是函数是编程带来的好处,只要满足一个函数模板,就可以给一个函数传多个功能不同的函数,完成不同的功能。

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/83785169