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}]
所以,這就是函式是程式設計帶來的好處,只要滿足一個函式模板,就可以給一個函式傳多個功能不同的函式,完成不同的功能。