go receive 与方法本质

 

引用:https://zhuanlan.zhihu.com/p/570714405

 

GO语言虽然不支持面向对像语法元素,比如类,对像,继承等,但Go语言也有方法.和函数相比,Go语言中的方法在声明形式上仅仅多了一个参数,称之为receiver参数.
receiver参数是方法与类型之间的纽带.
方法声明如下:

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

上面方法声明中的T称为receiver 的基类型.通过receiver,上述方法被绑定到类型T上,换句话说,上述方法是类型T的一个方法,通过类型T或*T的实实例调用该方法.
举个栗子:

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

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

方法的特点:
1 方法名的首字母是否大写决定了该方法是不是导出方法.
2 方法定义要与类型定义放在同一个包内.不能为原生类型,如int,float64,map等添加方法,只能为自定义类型定义方法
举个栗子:

//错误
func (i int ) String() string{
    return fmt.Sprintf("%d",i)
}

//正确
type MyInt int

func (i MyInt) String() string{
    return fmt.Sprintf("%d",int(i))
}

不能模跨Go包为其它包内的自定义类型定义方法
3 每个方法只能有一个receiver参数,不支持多receiver参数列表或变长receiver参数.一个方法只能绑定一个基类型,Go语言不支持同时绑定多个类型的方法.
4 receiver 参数的基类型本身不能是指针类型或接口类型
举个栗子

type MyInt *int //编译错误
func (r MyInt) String() string{
    return fmt.Sprintf("%d", *(*int)(r))
}

type MyReader io.Reader  
//错误
func (r MyReader) Read(p []byte) (int,error){
    ...
}

1. 方法的本质

type T struct{
    a int
}

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

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

将receiver作为第一个参数传入方法,上面的方法可以等价转换为下面的普通函数:

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

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

转换后的函数是方法的原型,只不过在GO语言中,这种等价转换是由go编译器在编译和生成代码时自动完成的,Go语言规范中提供了一个新概念,更充分理解等价转换.
一般使用方法如下:

var t T 
t.Get()
t.Set(1)

等价替换

var t T
T.Get(t)
(*T).Set(&t,1)

这种直接以类型名T调用方法的表达方式称为方法表达式. 这也是Go方法的本质,一个以方法所绑定类型实例为第一个参数的普通函数.

2. 选择recever的类型

了解:类型为T时,选择值类型的receiver ,类型为*T时,选择指针类型的receiver

package main

type T struct{
    a int
}

func (t T) M1(){

}

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

func main(){
    var t T
    t.M1()
    t.M2()

    var pt = &T{}
    pt.M1()
    pt.M2()
}

以上程序无法t 调用*T的M2方法,还是*T类型实例pt调用T的M1方法都是可以的.
由此得出receiver类型选用:
1 如果要对类型实例进行修改,为recerver选择*T类型
2如果没有对类型实例修改的需求,那么为receiver选择T类型或*T类型均可.

3. 基于对GO方法本质的代码理解

分析代码:

package main

import (
    "fmt"
    "time"
)

type f struct {
    name string
}

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

func main() {
    d1 := []*f{{"a"}, {"b"}, {"c"}}
    for _, v := range d1 {
        go v.print()
    }

    d2 := []f{{"d"}, {"e"}, {"f"}}
    for _, v := range d2 {
        go v.print()
    }
    time.Sleep(3 * time.Second)
}

运行结果:

$ go run main.go
a
b
c
f
f
f

为什么d2输出结果是3个 "f",而不是"d","e","f"?
根据方法的本质[一个以方法所绑定类型实例为第一个参数的普通函数,对这个程序做个等价变换]

func main() {
    d1 := []*f{{"a"}, {"b"}, {"c"}}
    for _, v := range d1 {
        go (*f).print(v)
    }

    d2 := []f{{"d"}, {"e"}, {"f"}}
    for _, v := range d2 {
        go (*f).print(&v)
    }
    time.Sleep(3 * time.Second)
}

1 迭代d1 时 由于d1中的元素类型是f指针,因此赋值后v就是元素地址
2 迭代d2时,由于d2中的元素类型是非指针,需要将其地址后再传入.这样每次传入的&v实际上是变量v的地址,for range 循环变量复用,v是元素f的副本.至此拨开乌云见天日
原程序按期望输出 将f类型的方法的 receiver类型由 *f 改为 f

 

参考:

 

posted @ 2024-05-09 11:27  redrobot  阅读(5)  评论(0编辑  收藏  举报