方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。

在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。

来看个例子:

 1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

package main

import "fmt"

type   Person struct {
            age int
    }

func   (p Person) howOld() int {
            return p.age
    }

func   (p *Person) growUp() {
            p.age += 1
    }

func   main() {
            // qcrao 是值类型
            qcrao := Person{age: 18}

// 值类型 调用接收者也是值类型的方法
            fmt.Println(qcrao.howOld())

// 值类型 调用接收者是指针类型的方法
            qcrao.growUp()
            fmt.Println(qcrao.howOld())

// ----------------------

// stefno 是指针类型
            stefno := &Person{age: 100}

// 指针类型 调用接收者是值类型的方法
            fmt.Println(stefno.howOld())

// 指针类型 调用接收者也是指针类型的方法
            stefno.growUp()
              fmt.Println(stefno.howOld())
    }

上例子的输出结果是:

1
    2
    3
    4

18
    19
    100
    101

调用了 growUp 函数后,不管调用者是值类型还是指针类型,它的 Age 值都改变了。

实际上,当类型和方法的接收者类型不同时,其实是编译器在背后做了一些工作,用一个表格来呈现:

-

值接收者

指针接收者

值类型调用者

方法会使用调用者的一个副本,类似于“传值”

使用值的引用来调用方法,上例中,qcrao.growUp()   实际上是 (&qcrao).growUp()

指针类型调用者

指针被解引用为值,上例中,stefno.howOld()   实际上是 (*stefno).howOld()

实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针

值接收者和指针接收者 #

前面说过,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用,这里面实际上通过语法糖起作用的。

先说结论:实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法;而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法。

来看一个例子,就会完全明白:

 1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

package main

import "fmt"

type   coder interface {
            code()
            debug()
    }

type   Gopher struct {
            language string
    }

func   (p Gopher) code() {
            fmt.Printf("I am coding %s language\n", p.language)
    }

func   (p *Gopher) debug() {
            fmt.Printf("I am debuging %s   language\n", p.language)
    }

func   main() {
            var   c coder = &Gopher{"Go"}
            c.code()
            c.debug()
    }

上述代码里定义了一个接口 coder,接口定义了两个函数:

1
    2

code()
    debug()

接着定义了一个结构体 Gopher,它实现了两个方法,一个值接收者,一个指针接收者。

最后,我们在 main 函数里通过接口类型的变量调用了定义的两个函数。

运行一下,结果:

1
    2

I am coding Go language
    I am debuging Go language

但是如果我们把 main 函数的第一条语句换一下:

1
    2
    3
    4
    5

func   main() {
            var   c coder = Gopher{"Go"}
            c.code()
            c.debug()
    }