Go語言函數型別

2020-07-16 10:05:08
在對Go語言的型別系統做了全面的講解後,本節將對函數型別進行全面深入的介紹,首先介紹“有名函數”和“匿名函數”兩個概念,使用 func FunctionName() 語法格式定義的函數我們稱為“有名函數”,這裡所謂的有名是指函數在定義時指定了“函數名”,與之對應的是“匿名函數”,所謂的匿名函數就是在定義時使用 func() 語法格式,沒有指定函數名,通常我們所說的函數指的是“有名函數”。

函數型別也分兩種,一種是函數位面量型別(未命名型別),另一種是函數命名型別。

函數位面量型別

函數位面量型別的語法表達格式是 func(InputTypeList)OutputTypeList,“有名函數”和“匿名函數”的型別都屬於函數位面量型別,有名函數的定義相當於初始化一個函數位面量型別後將其賦值給一個函數名變數,“匿名函數”的定義也是直接初始化一個函數位面量型別,只是沒有系結到一個具體函數名變數上。從 Go 型別系統的角度來看,“有名函數”和“匿名函數”都是函數位面量型別的範例。

函數命名型別

從前面章節知道可以使用 type NewType OldType 語法定義一種新型別,這種型別都是命名型別,同理可以使用該方法定義一種新型別——函數命名型別,簡稱函數型別,例如:

type NewFuncType FuncLiteral

依據Go語言型別系統的概念,NewFuncType 為新定義的函數命名型別,FuncLiteral 為函數位面量型別,FuncLiteral 為函數型別 NewFuneType 的底層型別,當然也可以使用 type 在一個函數型別中再定義一個新的函數型別,這種用法在語法上是允許的,但很少這麼使用,例如:

type NewFuncType OldFuncType

函數簽名

有了上面的基礎,函數簽名就比較好理解了,所謂“函數簽名”就是“有名函數”或“匿名函數”的字面量型別,所以有名函數和匿名函數的函數簽名可以相同,函數簽名是函數的“字面量型別”,不包括函數名。

函數宣告

Go語言沒有C語言中函數宣告的語意,準確地說,Go 程式碼呼叫 Go 編寫的函數不需要宣告,可以直接呼叫,但 Go 呼叫組合語言編寫的函數還是要使用函數宣告語句,範例如下。

//函數宣告=函數名+函數簽名
//函數簽名
func (InputTypeList)OutputTypeList
//函數宣告
func FuncName (InputTypeList)OutputTypeList

下面通過一個具體的範例來說明上述概念。
//有名函數定義,函數名是add
//add 型別是函數位面量型別 func(int, int) int
func add(a, b int) int {
    return a+b
}
//函數宣告語句,用於 Go 程式碼呼叫組合程式碼
func add(int, int) int
//add 函數的簽名,實際上就是 add 的字面量型別
func (int, int) int
//匿名函數不能獨立存在,常作為函數引數、返回值,或者賦值給某個變數
//匿名函數可以直接顯式初始化
//匿名函數的型別也是函數位面量型別 func (int, int) int
func (a,b int) int {
    return a+b
}
//新定義函數型別ADD
//ADD 底層型別是函數位面量型別 func (int, int) int
type ADD func (int, int) int
//add 和 ADD 的底層型別相同,並且 add 是字面量型別
//所以 add 可直接賦值給 ADD 型別的變數 g
var g ADD = add
func main() {
    f := func(a, b int) int {
        return a + b
    }
    g(1, 2)
    f(1, 2)
    //f 和 add 的函數簽名相同
    fmt.Printf("%Tn", f)   // func(int, int) int
    fmt.Printf("%Tn", add) // func(int, int) int
}
前面談到字面量型別是一種未命名型別 (unnamed type),其不能定義自己的方法,所以必須顯式地使用 type 宣告一個有名函數型別,然後為其新增方法,通常說的函數型別就是指有名函數型別,“函數簽名”是指函數的字面量型別,在很多地方把函數型別和函數簽名等價使用,這是不嚴謹的。

由型別轉換的規則可知,這兩種型別的底層型別相同,並且其中一個是字面量型別,二者是可以相互轉換的,下面來看一下經典的 http 標準庫對函數型別的實現,進一步理解這種用法。
//src/net/http/server.go
//定義一個有名函數型別 HandlerFune
type HandlerFunc func(ResponseWriter, *Request)

//為有名的函數型別新增方法
//這是一種包裝器的程式設計技巧
//ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
//函數型別 HandlerFunc 實現了介面 Handler 的方法
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
func (mux *ServeMux) Handle(pattern string, handler Handler)
//所以 HandlerFunc 型別的交量可以傳遞給 Handler 介面變數
func (mux *ServeMux) HandleFune (pattern string, handler func (ResponseWriter,
*Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}
通過 http 標準庫裡面對於函數型別的使用,我們可以看到函數型別的如下意義:
  • 函數也是一種型別,可以在函數位面量型別的基礎上定義一種命名函數型別。
  • 有名函數和匿名函數的函數簽名與命名函數型別的底層型別相同,它們之間可以進行型別轉換。
  • 可以為有名函數型別新增方法,這種為一個函數型別新增方法的技法非常有價值,可以方便地為一個函數增加“攔截”或“過濾”等額外功能,這提供了一種裝飾設計模式。
  • 為有名函數型別新增方法,使其與介面打通關係,使用介面的地方可以傳遞函數型別的變數,這為函數到介面的轉換開啟了大門。