Go Wiki:MethodSets
目錄
簡介
在 Go 中,特定類型或值的函式組特別重要,因為函式組決定值實作哪些介面。
規範
Go 語言規範 中有兩個關於函式組的重要條款。如下所示
函式組:類型可以與它關聯的函式組。介面類型的函式組是它的介面。任何其他命名 type T
的函式組包含所有接收器類型為 T
的函式。對應指標類型 *T
的函式組是所有接收器為 *T
或 T
的函式組(也就是說,它也包含 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 的一部分。