Go 学习笔记(61)— Go 高阶函数、函数作为一等公民(函数作为输入参数、返回值、变量)的写法
函數在 Go 語言中屬于“一等公民(First-Class Citizen)”擁有“一等公民”待遇的語法元素可以如下使用
- 可以存儲在變量中;
- 可以作為參數傳遞給函數;
- 可以在函數內部創建并可以作為返回值從函數返回;
1. 函數可以存儲在變量中
var (myFprintf = func(w io.Writer, format string, a ...interface{}) (int, error) {return fmt.Fprintf(w, format, a...)}
)func main() {fmt.Printf("%T\n", myFprintf) // func(io.Writer, string, ...interface {}) (int, error)myFprintf(os.Stdout, "%s\n", "Hello, Go") // 輸出Hello,Go
}
2. 作為參數傳入函數
標準庫 time 包的 AfterFunc 函數,就是一個接受函數類型參數的典型例子。你可以看看下面這行代碼,這里通過 AfterFunc 函數設置了一個 2 秒的定時器,并傳入了時間到了后要執行的函數。這里傳入的就是一個匿名函數:
time.AfterFunc(time.Second*2, func() { println("timer fired") })
package mainimport "fmt"type CalculateType func(int, int) intfunc add(a, b int) int {return (a + b)
}func mul(a, b int) int {return (a * b)
}func Calculate(a, b int, f CalculateType) int {return f(a, b)
}
func main() {a, b := 2, 4fmt.Println(Calculate(a, b, add)) // 6fmt.Println(Calculate(a, b, mul)) // 8
}
以上例子,Calculate 的 f 參數類型為 CalculateType, add 和 mul 函數具有和 CalculateType 函數類型相同的參數和返回值,因此可以將 add 和 mul 函數作為參數傳入 Calculate 函數中。
3. 支持在函數內創建并通過返回值返回
func setup(task string) func() {println("do some setup stuff for", task)return func() {println("do some teardown stuff for", task)}
}func main() {teardown := setup("demo")defer teardown()println("do some bussiness stuff")
}
4. 擁有自己的類型
在前面講解函數聲明時,我們曾得到過這樣一個結論:每個函數聲明定義的函數僅僅是對應的函數類型的一個實例,就像
var a int = 13
這個變量聲明語句中的 a,只是 int 類型的一個實例一樣。換句話說,每個函數都和整型值、字符串值等一等公民一樣,擁有自己的類型,也就是我們講過的函數類型。
下面代碼中的 HandlerFunc、visitFunc 就是 Go 標準庫中,基于函數類型進行自定義的類型:
// $GOROOT/src/net/http/server.go
type HandlerFunc func(ResponseWriter, *Request)// $GOROOT/src/sort/genzfunc.go
type visitFunc func(ast.Node) ast.Visitor
Go 語言中,可以把函數作為一種變量,用 type 去定義它,那么這個函數類型就可以作為值傳遞,甚至可以實現方法,我們可以利用這一特性進行類型轉換。
作為值傳遞的條件是類型具有相同的參數以及相同的返回值。Go 語言的類型轉換基本格式如下:
type_name(expression)
代碼實現
package mainimport "fmt"type CalculateType func(int, int)func (c *CalculateType) Server() {fmt.Println("這是函數類型")
}func add(a, b int) {fmt.Println(a + b)
}func mul(a, b int) {fmt.Println(a * b)
}func main() {a := CalculateType(add)b := CalculateType(mul)a(2, 4)b(2, 4)a.Server()b.Server()
}
輸出結果:
6
8
這是函數類型
這是函數類型
5. 高階函數
先來說說什么是高階函數?簡單地說,高階函數可以滿足下面的兩個條件之一:
- 接受其他的函數作為參數
- 把其他的函數作為結果返回
只要滿足了其中任意一個特點,我們就可以說這個函數是一個高階函數。高階函數也是函數式編程中的重要概念和特征。
高階函數和閉包
- 所謂閉包就是一個函數體內部引用了一個外部的變量
- 高階函數和函數式編程的特點就是 支持函數作為參數或者返回值
示例:
package mainimport ("errors""fmt"
)type operate func(x, y int) int// 方案1。
func calculate(x int, y int, op operate) (int, error) {// 函數類型屬于引用類型,它的值可以為nil,而這種類型的零值恰恰就是nil。if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil
}// 方案2。
type calculateFunc func(x int, y int) (int, error)func genCalculator(op operate) calculateFunc {/*閉包定義:在一個函數中存在對外來標識符的引用。所謂的外來標識符,既不代表當前函數的任何參數或結果,也不是函數內部聲明的,它是直接從外邊拿過來的。*//*此處 return 函數是個閉包,它里面使用的變量op既不代表它的任何參數或結果也不是它自己聲明的,而是定義它的genCalculator函數的參數,所以是一個自由變量。*/return func(x int, y int) (int, error) {if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil}
}func main() {// 方案1。x, y := 12, 23op := func(x, y int) int {return x + y}result, err := calculate(x, y, op)fmt.Printf("The result: %d (error: %v)\n",result, err)result, err = calculate(x, y, nil)fmt.Printf("The result: %d (error: %v)\n",result, err)// 方案2。x, y = 56, 78add := genCalculator(op)result, err = add(x, y)fmt.Printf("The result: %d (error: %v)\n",result, err)
}
Go 可以定義函數類型的變量,函數類型的變量可以被調用。定義函數類型變量示例代碼:
package mainimport ("fmt""reflect"
)func main() {var f = func(str string) {fmt.Println("hello", str)}fmt.Println("類型是:", reflect.ValueOf(f).Kind())f("func type")
}
輸出結果:
類型是: func
hello func type
等價于
package mainimport ("fmt""reflect"
)func demo(str string) {fmt.Println("hello", str)
}
func main() {var f func(str string)f = demofmt.Println("類型是:", reflect.ValueOf(f).Kind())f("func type")
}
函數類型變量也可以當做參數傳遞給另一個函數,然后在另一個函數中執行,示例代碼如下:
package mainimport ("fmt""reflect"
)func exec(f func(str string)) {f("func type")
}func main() {var f = func(str string) {fmt.Println("hello", str)}fmt.Println("類型是:", reflect.ValueOf(f).Kind())exec(f)
}
輸出結果:
類型是: func
hello func type
exec 函數接收一個函數類型的參數,那么是不是只要是函數,就可以被當做參數傳入到 exec 中嗎?
答案是:不行。從 exec 函數的參數列表可知,exec 接收一個函數類型參數,這個函數類型參數接收一個字符串類型的參數。
總結
以上是生活随笔為你收集整理的Go 学习笔记(61)— Go 高阶函数、函数作为一等公民(函数作为输入参数、返回值、变量)的写法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国动力锂电池用橡胶
- 下一篇: 2022-2028年中国酱腌菜行业市场研