Go Wiki:MethodSets

目录

简介

特定类型或值的 MethodSets 在 Go 中尤为重要,其中 MethodSets 确定值实现哪些接口。

规范

Go 语言规范 中有两个关于 MethodSets 的重要条款。它们如下所示

MethodSets:类型可能与其关联一个 MethodSets。接口类型的 MethodSets 是其接口。任何其他命名类型 T 的 MethodSets 由所有接收器类型为T 的方法组成。相应指针类型*T 的 MethodSets 是所有接收器为*TT 的方法的集合(即,它还包含T 的 MethodSets)。任何其他类型都有一个空的 MethodSets。在 MethodSets 中,每个方法必须具有唯一名称。

调用:方法调用x.m() 有效,如果(x 的类型)的 MethodSets 包含m,并且参数列表可以分配给m 的参数列表。如果x 可寻址且&x 的 MethodSets 包含m,则x.m()(&x).m() 的简写。

用法

在日常编程中,MethodSets 会在许多不同的情况下出现。其中一些主要情况包括对变量调用方法、对切片元素调用方法、对映射元素调用方法以及将值存储在接口中。

变量

一般来说,当您有一个类型的变量时,您几乎可以在其上调用任何您想要的内容。当您将上述两条规则结合在一起时,以下内容有效

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())
}

请注意,指针方法和值方法都可以对指针值和非指针值进行调用。要了解原因,让我们直接从规范中检查两种类型的 MethodSets

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 的一部分。