方法的定义
Go 中方法和函数在形式上很像,是作用在接收器上的一个函数,接收器是某种类型的变量。因此方法是一种特殊类型的函数,只是比函数多了一个接收器,在接口中定义的函数也称为方法。

type A struct {
    Face int
}

func (a A) f() {
    fmt.Println("hi", a.Face)
}

接收器

接收器类型除了不能是指针类型或接口类型外,可以是其他任何类型,不仅仅是结构体类型,也可以是函数类型,还可以是以 int、bool、string 等为基础的自定义类型。

package main

import (
    . "fmt"
)

type A struct {
    name string
}

func (a A) print() {
    Println(a.name)
}

func main() {
    var a A = A {name : "A"}
    b := &A {name : "B"}
    // b.print() 等价于 (*b).print()
    // 这是一种语法糖
    b.print()
    // A.print(a) 等价于 a.print()
    A.print(a)
    a.print()
}

程序输出

B
A
A

函数和方法的区别

方法相对于函数多了接收器,这是他们之间最大的区别。
函数是直接调用,而方法是作用在接收器上,方法需要类型的实例来调用。方法接收器必须有一个显式的名字,这个名字在方法中可以被使用。

指针方法与值方法

指针方法与值方法的区别

有类型 T 且方法的接收器为 (t T) 时称为值接收器,该方法称为值方法。方法的接收器为 (t *T) 时称为指针接收器,该方法称为指针方法。
如果想要方法改变接收器的数据,就在接收器的指针上定义该方法。否则,就在普通的值类型上定义方法,这是指针方法和值方法最大的区别。

package main

import (
    . "fmt"
)

type A struct {
    name string
}

func (a A) print1() {
    a.name = "changed1"
}

func (a *A) print2() {
    a.name = "changed2"
}

func main() {
    var a A = A {name : "B"}
    a.print1()
    Println(a.name)
    // 语法糖将值隐式转换为指针
    a.print2()
    Println(a.name)
}

程序输出

B
changed2

无论声明方法的接收器是指针接收器还是值接收器,Go 都可以将其隐式转换为正确的方法使用。

接口变量上的指针方法与值方法

package mian

type A struct {
    name string
}

type B interface {
    func1()
    func2()
}

func (a A) func1() {
    a.name = "A"
}

func (a *A) func2() {
    a.name = "*A"
}

func main() {
    var a A = A {"B"}
    a.func1()
    a.func2()

    // 类型 A 没有实现接口 B,无法直接赋值
    // cannot use b (type B) as type A in assignment: B does not implement A (func2 method has pointer receiver)
    // var b B = a
    var b B = &a
    b.func1()
    b.func2()
}

Go 有关接口与方法的规则如下

  1. 如果使用指针方法来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。
  2. 如果使用值方法来实现一个接口,那么那个类型的值和指针都能够实现对应的接口。

方法提升规则

package main

import (
    . "fmt"
)

type A struct {
    name string
}

type B struct {
    A
    // *A
}

func (a A) func1() {
    a.name = "A"
}

func (a *A) func2() {
    a.name = "*A"
}

func main() {
    var b B = B {A{name : "B"}}
    b.func1()
    b.func2()
}

程序输出

A
*A
// A
// *A

给定一个结构体 A 和 一个类型 B,Go 中匿名嵌入类型的方法集提升规则如下

  1. 如果 A 包含匿名字段 B,则 A 和 *A 的方法集都包含具有接收器 B 的提升方法,*A的方法集还包含具有接收器 *B 的提升方法。
  2. 如果 A 包含匿名字段 *B,则 A 和 *A 的方法集都包含具有接收器 B 或 *B 的提升方法。

注意:以上规则由于 A.Meth() 会被自动转换为 (&A).Meth() 这个语法糖,导致误解规则不起作用,而实际上规则是有效的。