Go Wiki: 方法集

目錄

引言

在 Go 語言中,特定型別或值的 **方法集** 至關重要,因為方法集決定了一個值實現了哪些介面。

規範

Go 語言規範中關於方法集有兩條重要的條款。它們如下:

方法集:一個型別可以關聯一個方法集。介面型別的 **方法集** 就是它本身。任何其他命名型別 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) 

請注意,雖然從上面的程式可以看到你可以無問題地呼叫 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 的一部分。