1. 程式人生 > >GO學習筆記——函數語言程式設計(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 + " " + 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}]

所以,這就是函式是程式設計帶來的好處,只要滿足一個函式模板,就可以給一個函式傳多個功能不同的函式,完成不同的功能。