7.3 方法调用

普通的函数调用

普通的函数调用跟C语言中的调用方式基本上是一样的,除了多值返回的一些细微区别,见前面章节。

对象的方法调用

根据Go语言文档,对象的方法调用相当于普通函数调用的一个语法糖衣。

  1. type T struct {
  2. a int
  3. }
  4. func (tv T) Mv(a int) int { return 0 } // value receiver
  5. func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
  6. var t T

表达式

  1. T.Mv

得到一个函数,这个函数等价于Mv但是带一个显示的接收者作为第一个参数,也就是

  1. func(tv T, a int) int

下面这些调用是等价的:

  1. t.Mv(7)
  2. T.Mv(t, 7)
  3. (T).Mv(t, 7)
  4. f1 := T.Mv; f1(t, 7)
  5. f2 := (T).Mv; f2(t, 7)

可以看了一下方法调用用生成的汇编代码:

  1. type T int
  2. func (t T) f() {
  3. fmt.Println("hello world!\n")
  4. }
  5. func main() {
  6. var v T
  7. v.f()
  8. return
  9. }

将它进行汇编:

  1. go tool 6g -S test.go

得到的汇编代码是:

  1. 0044 (sum.go:15) TEXT main+0(SB),$8-0
  2. 0045 (sum.go:15) FUNCDATA $0,gcargs·1+0(SB)
  3. 0046 (sum.go:15) FUNCDATA $1,gclocals·1+0(SB)
  4. 0047 (sum.go:16) MOVQ $0,AX
  5. 0048 (sum.go:17) MOVQ AX,(SP)
  6. 0049 (sum.go:17) CALL ,T.f+0(SB)
  7. 0050 (sum.go:18) RET ,

从这段汇编代码中可以看出,方法调用跟普通函数调用完全没有区别,这里就是把v作为第一个参数调用函数T.f()。

组合对象的方法调用

在Go中没有继承,但是有结构体嵌入的概念。将一个带方法的类型匿名嵌入到另一个结构体中,则这个结构体也会拥有嵌入的类型的方法。

这个功能是如何实现的呢?其实很简单。当一个类型被匿名嵌入结构体时,它的方法表会被拷贝到嵌入结构体的Type的方法表中。这个过程也是在编译时就可以完成的。对组合对象的方法调用同样也仅仅是普通函数调用的语法糖衣。

接口的方法调用

接口的方法调用跟上述情况略有不同,不同之处在于它是根据接口中的方法表得到对应的函数指针,然后调用的,而前面是直接调用的函数地址。

对象的方法调用,等价于普通函数调用,函数地址是在编译时就可以确定的。而接口的方法调用,函数地址要在运行时才能确定。将具体值赋值给接口时,会将Type中的方法表复制到接口的方法表中,然后接口方法的函数地址才会确定下来。因此,接口的方法调用的代价比普通函数调用和对象的方法调用略高,多了几条指令。