Go进阶之理解方法本质

15 阅读4分钟

Go语言虽然不支持经典的面向对象的语法元素.比如继承 对象和类.Go语言也有方

法.和函数相比就是在声明形式上多了一个参数.Go称为receiver参数.receiver是参

数与类型之间的纽带.

方法声明格式:

func (receiver T/* T) MethodName(参数列表) (返回值列表){
	//方法体
}

方法声明的T称为receiver的基类型.通过receiver.上述方法被绑定到类型T上.伪代码如下:

var t T
t.methodName(参数列表)

var pt *T = &t
pt.methodName(参数列表)

Go方法特点:

1).方法名的首字母是否大写决定了该方法是不是导出方法.

2).方法定义要与类型定义放在同一个包内.由此可以知道不能为原生类型(如int

float64 map等)添加方法.只能为自定义类型定义方法.示例如下.

type MyInt int64

func (i MyInt) String() string {
	return "自定义类型的方法"
}

同理可以得出不能跨域Go包为其他包内的自定义类型定义方法.

3).每个方法只能有一个receiver参数.不支持多receiver参数列或变长receiver参

数.一个方法只能绑定一个基类型.Go语言不支持同时绑定多个类型的方法.

4).receiver参数的基类型本身不能是指针类型或者接口类型.示例如下.

image.png

image.png

2.方法的本质:

Go语言没有类.方法与类别通过reveiver联系到一起,可以为任何非内置原生类型定

义方法.示例如下:

type T struct {
	a int
}

func (t T) get() int {
	return t.a
}

func (t *T) set(a int) {
	t.a = a
}

receiver其实是作为第一个参数传入方法列表中.上述的方法可以等价转换为下面的

函数,代码如下:

func Get(t T) int {
    return t.a
}

func Set(t *T, a int) {
    t.a = a
}

这种转换后的函数就是方法的原型.只不过在Go语言中.这种等价转换是由Go编译器

在编译和生成代码时自动完成的.Go语言规范中提供了一个新的概念.可以更充分理解

等价转换.

Go方法一般使用方式:

func Test() {
	var t T
	t.get()
	t.set(1000)
}

等价替换:

func ChangeTest() {
    var t T
    T.get(t)
    (*T).set(&t, 1000)
}

这种直接以类型名T调用方法的表达式被称为方法表达式.类型T只能调用T的方法集

合(Method Set)中的方法.同理.T只能调用T的方法集合中的方法.这种通过方法表

达式进行调用的方式与我们之前所做的方法到函数的等价转换如出一辙.这就是Go方

法的本质.一个方法所绑定类型实例为第一个参数的普通函数.

Go方法本身就是一个普通函数.我们甚至可以把它作为右值赋值给函数类型的变量.

func Test() {
	var t T

	f1 := (*T).set
	f2 := T.get
	f1(&t,1)
	fmt.Println(f2(t))
}

3.选择正确的reveiver类型:

方法和函数的等价变换公式:

func(t T) M1()<=> M1(t T)
	func(t *T) M2()<=> M2(t *T)

M1方法的receiver参数类型为T.M2方法的receiver参数类型为T.*

1).receiver类型为T时:

选择以T作为receiver参数类型时.T的M1方法等价于M1(t T).Go函数的参数采用的

是值复制传递.也就是说M1函数体中的t是T类型的一个副本.这样在M1函数实现中对

参数t做任何修改都只会影响副本.不会影响到原T类型.

2).receiver类型为T时:*

选择以T作为receiver参数类型时.T的M2方法等价为M2(t T).我们传递给M2函

数的t是T类型实例的地址.这样M2函数体中对参数t做的任何修改都会反应到原T类

型实例上.

示例如下:

```
func main() {
    //t.a=0
    var t T
    fmt.Println(t.a)
    t.M1()
    fmt.Println(t.a)
    t.M2()
    fmt.Println(t.a)
}

type T struct {
    a int
}

func (t T) M1() {
    t.a = 10
}

func (t *T) M2() {
    t.a = 11
}
```

执行结果:

疑惑:是不是T类型实例只能调用receiver为T的类型方法.不能调用receiver为T类*

型的方法呢.答案是否定.无论T类型实例还是T类型实例.都既可以调用receiver为T*

类型的方法.也可以调用receiver为T类型的方法.示例如下:*

```
func main() {
    var t T
    var pt = &T{}
    t.M1()
    t.M2()

    pt.M1()
    pt.M2()
}
```

可以看到上述都可以进行互相调用.实际上这都是Go语法糖.Go编译器在编译和生成

代码时为我们做了自动转换.

总结:

如果要对类型实例进行修改.可以为receiver选择T类型.*

如果没有对类型实例修改的需求.可以为receiver选择T类型或T类型均可.如果类型*

的size过大的话.选择指针会更好一些.

微笑着孤言寡语.





如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路