Go Wiki:MethodSets

目錄

簡介

在 Go 中,特定類型或值的函式組特別重要,因為函式組決定值實作哪些介面。

規範

Go 語言規範 中有兩個關於函式組的重要條款。如下所示

函式組:類型可以與它關聯的函式組。介面類型的函式組是它的介面。任何其他命名 type T 的函式組包含所有接收器類型為 T 的函式。對應指標類型 *T 的函式組是所有接收器為 *TT 的函式組(也就是說,它也包含 T 的函式組)。任何其他類型都有空函式組。在函式組中,每個函式都必須有唯一名稱。

呼叫:函式呼叫 x.m() 有效,如果 x(類型)的函式組包含 m,並且引數清單可以指派給 m 的參數清單。如果 x 可尋址且 &x 的函式組包含 m,則 x.m()(&x).m() 的簡寫。

用法

在日常程式設計中,函式組會在許多不同情況下出現。其中一些主要情況是在變數上呼叫函式、在切片元素上呼叫函式、在對應元素上呼叫函式,以及在介面中儲存值。

變數

一般來說,當您有一個類型變數時,您幾乎可以呼叫任何您想要的變數。當您將上述兩個規則結合在一起時,以下內容是有效的

type List []int

func (l List) Len() int        { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }

func main() {
    // A bare value
    var lst List
    lst.Append(1)
    fmt.Printf("%v (len: %d)\n", lst, lst.Len())

    // A pointer value
    plst := new(List)
    plst.Append(2)
    fmt.Printf("%v (len: %d)\n", plst, plst.Len())
}

請注意,指標方法和值方法都可以呼叫指標和非指標值。要了解原因,讓我們直接從規格中檢查兩種類型的函式集

List
- Len() int

*List
- Len() int
- Append(int) 

請注意,List 的函式集實際上不包含 Append(int),即使您可以從上述程式中看到,您可以毫無問題地呼叫該函式。這是上述第二個規格部分的結果。它會隱式地將以下第一行轉換為第二行

lst.Append(1)
(&lst).Append(1)

現在,小數點前的值是 *List,其函式集包括 Append,並且呼叫是合法的。

為了讓您更容易記住這些規則,將指標和值接收函式與函式集分開考慮可能很有幫助。在任何已經是指標或可以取得其位址(就像上述範例中一樣)的任何內容上呼叫指標值函式是合法的。在任何值或可以解除其參考的值(就像任何指標一樣;這種情況在規格中明確指定)上呼叫值函式是合法的。

切片元素

切片元素幾乎與變數相同。由於它們可以定址,因此指標和值接收函式都可以呼叫指標和值元素切片。

對應元素

映射元素不可定址。因此,以下是一個非法操作

lists := map[string]List{}
lists["primes"].Append(7) // cannot be rewritten as (&lists["primes"]).Append(7)

然而,以下仍然有效(而且是更常見的情況)

lists := map[string]*List{}
lists["primes"] = new(List)
lists["primes"].Append(7)
count := lists["primes"].Len() // can be rewritten as (*lists["primes"]).Len()

因此,指標接收器方法和值接收器方法都可以呼叫指標元素映射,但只有值接收器方法可以呼叫值元素映射。這是使用結構元素建立的映射幾乎總是使用指標元素的原因。

介面

儲存在介面中的具體值無法定址,就像映射元素無法定址一樣。因此,當您在介面上呼叫方法時,它必須具有相同的接收器類型,或者必須直接從具體類型中辨識:指標接收器方法和值接收器方法可以分別使用指標和值呼叫,正如您所預期的。值接收器方法可以使用指標值呼叫,因為它們可以先解除參考。但是,指標接收器方法無法使用值呼叫,因為儲存在介面中的值沒有位址。在將值指定給介面時,編譯器會確保所有可能的介面方法實際上都可以對該值呼叫,因此嘗試進行不當指定會在編譯時失敗。為了擴充先前的範例,以下說明什麼是有效的,什麼是無效的

type List []int

func (l List) Len() int        { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }

type Appender interface {
    Append(int)
}

func CountInto(a Appender, start, end int) {
    for i := start; i <= end; i++ {
        a.Append(i)
    }
}

type Lener interface {
    Len() int
}

func LongEnough(l Lener) bool {
    return l.Len()*10 > 42
}

func main() {
    // A bare value
    var lst List
    CountInto(lst, 1, 10) // INVALID: Append has a pointer receiver
    if LongEnough(lst) {  // VALID: Identical receiver type
        fmt.Printf(" - lst is long enough")
    }

    // A pointer value
    plst := new(List)
    CountInto(plst, 1, 10) // VALID: Identical receiver type
    if LongEnough(plst) {  // VALID: a *List can be dereferenced for the receiver
        fmt.Printf(" - plst is long enough")
    }
}

此內容是 Go Wiki 的一部分。