Loading

Go语言精进之路读书笔记第23条——理解方法的本质以选择正确的receiver类型

和函数相比,Go语言中的方法在声明形式上仅仅多了一个参数,Go称之为receiver参数。receiver参数是方法与类型之间的纽带。

Go方法特点:

  • 方法名的首字母是否大写决定了该方法是不是导出方法。
  • 方法定义要与类型定义放在同一个包内。由此可以推出,不能为原生类型(如int/float64/map等)添加方法,只能为自定义类型定义方法。不能横跨Go包为其他包内的自定义类型定义方法。
  • 每个方法只能有一个receiver参数,不支持多receiver参数列表,不支持变长receiver参数,不支持同时绑定多个类型。
  • receiver参数的基类型本身不能是指针类型或接口类型

23.1 方法的本质

type T struct {
    a int
}
func (t T) Get() int {
    return t.a
}
func (t *T) Set(a int) int {
    t.a = a
    return t.a
}

// 等价转换,将receiver作为第一个参数传入方法的参数列表,转换之后就是方法的原型
func Get(t T) int {
    return t.a
}
func Set(t *T, a int) int {
    t.a = a
    return t.a
}
// 使用方法
var t T
t.Get()
t.Set(1)
// 等价替换为
var t T
T.Get(t)
(*T).Set(&t, 1)

直接以类型名T调用方法的表达方式被称为方法表达式(Method Expression)

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

23.2 选择正确的receiver类型

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

Go函数的参数采用的是值复制传递,M1函数体中的t是T类型实例的一个副本,M2函数体中的t是T类型实例的地址

无论是T类型实例还是*T类型实例,都可以调用receiver为T类型或*T类型的方法。实际上是Go语法糖,Go编译器在编译和生成代码时为我们做了自动转换

初步结论:

  • 如果要对类型实例进行修改,使用*T类型
  • 如果没有修改的需求,使用T类型和*T类型均可。如果考虑Go方法调用时,receiver是以值复制的形式传入方法中的,选择*T类型会减少损耗

23.3 基于对Go方法本质的理解巧解难题

  • data1输出:one two tree
    • 迭代data1时,由于data1中的元素类型是field指针(*field),因此赋值后v就是元素地址,每次调用print时传入的参数(v)实际上也是各个field元素的地址
  • data2输出:six six six
    • 迭代data2时,由于data2中的元素类型是field(非指针),需要将其取地址后再传入,这样每次调用print时传入的参数(&v)实际上是变量v的地址,而不是切片data2中各元素的地址
    • 在整个for range过程中v只有一个,因此data2迭代完成之后,v是元素“six”的副本

这个问题和Go语言精进之路读书笔记第19条——理解Go语言表达式的求值顺序中19.2 for range的避"坑"指南的1.迭代变量的重用还是不太一样

  • 19条中的是闭包函数使用了外层的变量,而外层变量只有一份,导致最终输出的结果相同
  • 上面的问题是调用print时传入的参数实际内容不一样,data1中传入的是各个field元素的地址,每次循环都不一样;而data2中传入的是变量v的地址,每次循环都一样
type field1 struct {
    name string
}

func (p *field1) print() {
    fmt.Println(p.name)
}

func main() {
    data1 := []*field1{{"one"}, {"two"}, {"three"}}
    for _, v := range data1 {
        go v.print()
    }

    data2 := []field1{{"four"}, {"five"}, {"six"}}
    for _, v := range data2 {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}
posted @ 2024-02-13 11:51  brynchen  阅读(8)  评论(0编辑  收藏  举报