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