?1.什么是 Go 中的一等函數
任何一門編程語言都離不開函數,無論是命令式語言 C、多范式編程語言 C++,還是面向對象編程語言 Java、Ruby,動態語言 Python、JavaScript,函數這一語法元素都是當仁不讓的核心。
Go 語言沒有面向對象語言的語法,比如類 、繼承、對象,但 Go 語言中最重要的部分就是支持一等函數。
在 Go 語言中,函數式唯一一種基于特定輸入、實現特定任務并可反饋任務執行結果的代碼塊。本質上 Go 程序就是一組函數的集合。
什么是一等函數
一等函數允許將函數分配給變量(將函數通過變量進行傳遞),作為參數傳遞給其他函數,并從其他函數返回。
2.匿名函數
讓我們從一個簡單的例子開始,它將一個函數分配給一個變量。
package main
import(
"fmt"
)
func main() {
a := func() {
fmt.Println("Learning first class Function")
}
a()
fmt.Printf("%T", a)
}
在上面的程序中,我們利用 a := func()? 給變量 a 分配了一個函數,這是將一個函數賦值給一個變量的語法。
然后我們分配給 a 的函數并沒有名字,這類函數就被稱為匿名函數。
調用這個函數的唯一方法就是使用變量 a?,所以在后面使用 a()? 來調用這個函數,這就會打印出 Learning first class Function;
然后我們打印變量 a? 的類型,這將打印出 func()。
運行結果:
Learning first class Function
func()
也可以調用匿名函數而不把它賦值給一個變量,讓我們來看一下下面的例子是如何做到這一點的:
package main
import (
"fmt"
)
func main() {
func() {
fmt.Println("Learing first class Function")
}()
}
在上面的程序中,在第 8 行定義了一個匿名函數。緊接著我們在第 10 行用 () 調用該函數。這個程序將輸出:
Learing first class Function
也可以像其他函數一樣,向匿名函數傳遞參數:
package main
import (
"fmt"
)
func main() {
func(n string) {
fmt.Println("Welcome to", n)
}("Gophers's World!")
}
我們在上面的代碼中,向匿名函數中傳入一個 n string? 字符串參數,然后在調用時傳入一個 "Gophers's World!" ,此時,運行程序將得到如下的結果:
Welcome to Gophers's World!
3.用戶自定義的函數類型
就像我們自定義結構體類型一樣,在 Go 語言中也支持自定義函數類型:
type add func(a int, b int) int
上面的代碼片段創建了一個新的函數類型 add?,它接受兩個整數參數并返回一個整數,現在我們可以定義 add 類型的變量,如下的代碼:
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
}
sum := a(2022, 10)
fmt.Println("a + b = ", sum)
}
上面的程序中,我們定義了一個 add? 類型的變量,并給它分配了一個簽名與 add? 類型相符的函數,接著通過 sum := a(2022,10)? 調用并將結果賦給 sum,運行程序后得到如下的結果:
4.高階函數
對高階函數的定義是這個函數至少做到以下的某一項的功能:
將函數作為參數傳遞給其他函數
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? 函數,它接收兩個 int 參數,并返回一個 int 參數,然后把匿名函數傳給變量 f?,然后把 f 作為參數傳遞給 simple? 函數,最終這個程序將打印 67 輸出:
從其他函數中返回函數
現在讓我們重寫上面的程序,從 simple 函數中返回一個函數:
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(2022, 60))
}
運行該程序,得到結果;
5.閉包
閉包是匿名函數的一種特殊情況。閉包是匿名函數,它訪問定義在函數主體之外的變量。
代碼如下:
package main
import (
"fmt"
)
func main() {
a := 2022
func() {
fmt.Println("a = ", a)
}()
}
每個閉包都與它自己周圍的變量綁定。讓我們通過一個簡單的例子來理解這意味著什么。
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()
b := appendStr()
fmt.Println(a("World"))
fmt.Println(b("Everyone"))
fmt.Println(a("Gopher"))
fmt.Println(b("!"))
}
在上面的程序中,appendStr? 函數返回一個閉包。這個閉包被綁定到變量 t? 上,變量 a? 和 b? 是閉包,被綁定到它們自己的值 t 上。
我們傳遞參數 World? 給 a?,然后 a? 的值變成了 Hello World。
傳遞參數 Everyone? 給 b,然后 b? 的值變成了 Hello Everyone 。
Hello World
Hello Everyone
Hello World Gopher
Hello Everyone !
閉包通常也是支持嵌套和 defer 工作的方法。在下面的例子中,我們可以看到一個允許我們嵌套工作的函數閉包:
package main
import (
"fmt"
"sort"
)
func main() {
input := []string{"foo", "bar", "baz"}
var result []string
// closure callback
func() {
result = append(input, "abc")
result = append(result, "def")
sort.Sort(sort.StringSlice(result))
}()
fmt.Println(result)
}
運行結果:
6.一等函數的實際應用
到目前為止,我們已經定義了什么是第一類函數,我們也看到了一些精心設計的例子來學習它們是如何工作的。現在讓我們來寫一個具體的程序,展示第一類函數的實際用途。
我們將創建一個程序,根據一些標準來過濾一部分學生。讓我們一步一步地去做。
首先讓我們定義學生類型:
type student struct {
firstName string
lastName string
grade string
country string
}
下一步是編寫 filter 函數。這個函數以一個學生切片和一個確定學生是否符合過濾標準的函數為參數。如下:
func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) == true {
r = append(r, v)
}
}
return r
}
在上述函數中,filter? 的第二個參數是一個函數,它以一個 student 為參數,返回一個 bool 。這個函數確定一個特定的學生是否符合某個標準。我們在第 3 行遍歷學生切片。如果該函數返回真,則意味著該學生通過了過濾標準,并被添加到切片 r 中。
現在來看一個完整的程序:
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) == true {
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 {
if s.grade == "B" {
return true
}
return false
})
fmt.Println(f)
}
在主函數中,我們首先創建了兩個學生 s1 和 s2,并將他們添加到片斷 s 中。現在我們假設要找出所有成績為 B 的學生,在上述程序中,我們通過傳遞一個檢查學生是否為 B 級的函數,如果是,則返回 true。 上述程序將打印:
比方說,我們想找到所有來自印度的學生。這可以通過改變過濾器函數的參數來輕松實現。如下:
c := filter(s, func(s student) bool {
if s.country == "India" {
return true
}
return false
})
fmt.Println(c)
讓我們再寫一個程序來結束本文。這個程序將對一個切片的每個元素進行同樣的操作,并返回結果。
例如,如果我們想將一個切片中的所有整數乘以 5,并返回輸出結果,可以用第一類函數輕松完成。
這類對集合中每個元素進行操作的函數被稱為 map 函數。如下這個程序
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)
}
運行結果:
7.總結
在本文中,介紹了什么是一等函數的概念和功能,匿名函數、用戶自定義函數類型、高階函數和閉包,最后給出了一等函數的實際應用例子,希望這篇文章對你有所幫助!