方法

例子

变量a称为方法接收者,会作为方法Name()的第一个参数传入。

Go语言中函数类型只和参数与返回值相关。

方法本质上就是普通的函数,接收者是隐含的第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type A struct {
name string
}

func (a A) Name() string {
a.name = "Hi " + a.name
return a.name
}

func main() {
a := A{name: "itxiaoma"}
fmt.Println(a.Name()) //Hi itxiaoma
fmt.Println(A.Name(a)) //Hi itxiaoma
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type A struct {
name string
}

func (a A) Name() string {
a.name = "Hi " + a.name
return a.name
}

func NameOfA(a A) string {
a.name = "Hi " + a.name
return a.name
}

func main() {
t1 := reflect.TypeOf(A.Name)
t2 := reflect.TypeOf(NameOfA)
fmt.Println(t1) //func(main.A) string
fmt.Println(t2) //func(main.A) string
fmt.Println(t1 == t2) //true
}

方法调用

值接收者

  • main函数栈帧:局部变量a只包含一个string类型成员,字符串内容在数据段,地址addr1,字节数目4;
  • 传参值拷贝:局部变量a拷贝到参数空间,指向新的内容addr2,字节数目改为8
  • 局部变量a是值接收者,通过它调用方法时修改的是拷贝过去的参数,而不是局部变量a

image-20221226212840188

指针接收者

  • main函数栈帧:包含局部变量a,还有a的指针pa
  • pa.Name()会被转为(*A).Name(pa)这样的函数调用
  • 传参值拷贝:参数为A的指针,传参值拷贝时拷贝的是变量a的地址;修改时变量a指向新的地址addr2,字节数目改为8;
  • 拷贝返回值时,拷贝的是局部变量a的成员

image-20221226214236989

转换语法糖

语法糖是在编译期间发挥作用的,编译期间无法拿到地址的字面量就不能借助语法糖转换了,无法通过编译。

image-20221226214257204

方法赋给变量

Go语言中函数作为变量,参数和返回值时,都是以Function Value的形式存在的。

闭包只是有捕获列表的Function Value。

方法表达式

  • 把一个类型的方法赋给一个变量,该变量称为方法表达式(f1)
  • f1本质上也是一个Function Value,也就是一个funcval结构体的指针,fn指向A.GetName的函数指令入口
  • 通过f1执行方法时,需要传入A类型的变量a作为参数

方法变量

  • 方法变量也是一个Function Value,而且会捕获方法接收者形成闭包
  • 这里f2仅作为局部变量,与a的声明周期是一致的,编译器会优化为A.GetName(a)进行调用

image-20221226214333782

方法变量作为返回值:

f3就是一个闭包对象,捕获了GetFunc函数的局部变量a

image-20221226214359371

参考链接

幼麟实验室bilibili:https://space.bilibili.com/567195437/?spm_id_from=333.999.0.0