匿名函數
匿名函數(英語:Anonymous Function)在計算機編程中是指一類無需定義標識符(函數名)的函數或子程序,普遍存在於多種編程語言中。---wikipedia
Golang是支持匿名函數的,即在需要使用函數時,再定義函數,匿名函數沒有函數名,只有函數體,函數可以被作為一種類型被賦值給函數類型的變量,匿名函數往往以變量方式被傳遞
匿名函數經常被用於實現回調函數,閉包等.
匿名函數定義
匿名函數的定義就是: 沒有名字的普通函數
func (參數列表) (返回值列表) {
函數體
}
匿名函數的調用
1 在定義時調用匿名函數
匿名函數可以在聲明后直接調用; 例如:
package main
import "fmt"
func main() {
// 定義匿名函數並賦值給f變量
f := func(data int) {
fmt.Println("hello", data)
}
// 此時f變量的類型是func(), 可以直接調用
f(100)
}
匿名函數的用途非常廣泛,匿名函數本身是一種值,可以方便的保存在各種容器中實現回調函數和操作封裝
2 匿名函數做回調函數
回調函數,或簡稱回調(Callback 即call then back 被主函數調用運算后會返回主函數),是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用
匿名函數作為回調函數的設計在go語言的系統包中是很長見的;strings包中就有這種實現:
func TrimFunc(s string, f func(rune) bool) string {
return TrimRightFunc(TrimLeftFunc(s, f), f)
}
下面我們自己來寫個demo,領會回調函數的特點:
package main
import "fmt"
/*
目標:
對切片的遍歷操作,遍歷中訪問每個元素的操作使用匿名函數來實現.
用戶傳入不同的匿名函數體可以實現對元素的遍歷操作
*/
// 遍歷切片中每個元素,通過給定的函數進行元素訪問
func visit(list []int, f func(int)) {
for _, value := range list {
f(value)
}
}
func main() {
l := []int{1,2,3}
// 使用匿名函數打印切片的內容
visit(l, func(value int) {
fmt.Println(value)
})
}
匿名函數實現封裝
封裝(英語:Encapsulation)是指,一種將抽象性函數接口的實現細節部分包裝、隱藏起來的方法。同時,它也是一種防止外界調用端,去訪問對象內部實現細節的手段,這個手段是由編程語言本身來提供的.
下面這段代碼將匿名函數作為map的鍵值,通過命令函數參數動態調用匿名函數;
package main
import (
"fmt"
"flag"
)
// 使用flag包解析命令行傳入的參數
// flag包中解析出來的參數是 *string 類型的指針變量, 在調用值時不能直接調用,使用*取值
var skillParm = flag.String("skill", "", "skill to perform")
func main() {
// 解析參數; 解析后skillParm將執行命令行傳入的值
flag.Parse()
// 定義一個字符串映射到func()的map,然后填充這個map
var skill = map[string]func(){
"fire": func(){
fmt.Println("chicken fire")
},
"run": func() {
fmt.Println("soldier run")
},
"fly": func(){
fmt.Println("angle fly")
},
}
// 如果傳入的值存在就調用map的值(這里的值是一個匿名函數)進行打印
if f, ok := skill[*skillParm]; ok {
f()
} else {
fmt.Println("skill not found")
}
}
函數實現接口
接口在go語言中是被廣泛使用的,江湖有種說法"go是面向接口編程". 那么如果使用函數來實現和接口一樣的功能呢?
函數和其他類型一樣都屬於“一等公民”,其他類型能夠實現接口,函數也可以實現
我們來對比函數和結構體在實現接口上的異同點:
1 結構體實現接口
我們要實現的功能是: 這個接口需要實現Call()方法,調用會傳入一個interface{}類型的變量,這種類型的變量表示任意類型的值.
package main
import (
"fmt"
)
// 調用器接口,定義一個接口,實現Call()方法
type Invoker interface{
Call(interface{})
}
// 定義結構體類型, 未定義任何成員,能夠接收任意類型的數據
type Struct struct{}
// 實現Invoker的Call方法
func (s *Struct) Call(p interface{}) {
fmt.Println("from struct", p)
}
func main() {
// 聲明接口變量,將定義的Struct類型實例化,並傳入接口中進行調用
var invoker Invoker
// 實例話結構體
s := new(Struct) // <==> s := &Struct
// 將實例化的結構體賦值給接口
invoker = s
// 使用接口調用實例化結構體的方法Strct.Call()方法
invoker.Call("hello")
}
2 函數實現接口
函數實現接口: 函數的聲明不能直接實現接口,需要將函數定義為類型后,使用類型實現結構體, 當類型方法被調用時,還需要調用函數本體
package main
import (
"fmt"
)
// 調用器接口
type Invoker interface{
Call(interface{})
}
// 函數定義為類型, 將func(interface{})定義為FuncCaller類型;
// 這里只是定義了函數類型,需要函數本身進行邏輯處理.
// FuncCaller無需被實例化,只需要將函數轉換為FuncCaller類型即可,
// 函數來源可以是命名函數、匿名函數或閉包
type FuncCaller func(interface{})
// 實現Invoker的Call方法
func (f FuncCaller) Call(p interface{}) {
// 調用函數本體, FuncCaller的Call()方法被調用與func(interface{})無關,
// 還需要手動調用函數本體
f(p)
}
func main(){
// 聲明接口變量
var invoker Invoker
// 將匿名函數轉為FuncCaller類型,在賦值給接口
invoker = FuncCaller(func(v interface{}) {
fmt.Println("from function", v)
})
// 使用接口調用FuncCaller.Call,內部會調用函數本體
invoker.Call("hello")
}
系統包中的例子
在Go語言中,系統的某些包也是用函數來實現接口,例如HTTP包中就有的函數實現接口的例子;
HTTP包中包含有Handler接口定義,代碼如下:
type Handler interface {
ServeHTTP(Response Writer, *Request)
}
Handler 用於定義每個http的請求和相應的處理過程, 同時,也可以使用處理函數實現接口,定義如下:
type HandlerFunc func(ResponseWriter, *Request)
func(f HandlerFunc) ServeHTTP(W ResponseWriter, r *Request) {
f(w, r)
}
要使用閉包實現默認的HTTP請求處理,可以使用http.HandleFunc()函數, 函數定義如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
而 DefaultServeMux 是ServeMux結構,擁有HandlerFunc()方法,定義如下:
func (mux *ServeMux) HandlerFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handler(pattern, HandlerFunc(handler))
}
上面代碼將外部傳入的函數handler()轉為HandlerFunc類型,HandlerFunc類型實現了Handler的ServeHTTP方法,底層可以同時使用各種類型來實現Handler接口進行處理.
參考
Go語言從入門到進階實戰 - 徐波