Golang方法接收者

全栈码农画像

共 3411字,需浏览 7分钟

 ·

2022-06-02 18:57


  • [定义]:golang的方法(Method)是一个带有receiver的函数Function,Receiver是一个特定的struct类型,当你将函数Function附加到该receiver, 这个方法Method就能获取该receiver的属性和其他方法。

  • [面向对象]:golang方法Method允许你在类型上定义函数,是一个面向对象的行为代码, 这也有一些益处:同一个package可以有相同的方法名, 但是函数Function却不行。

func(receiver receiver_type)some_func_name(arguments)return_values

从应用上讲,方法接受者分为值接收者/指针接收者,初级golang学者可能看过这两个接收者实际表现, 但是一直很混淆,很难记忆。

本次我们使用地址空间的角度来剖析实质,强化记忆。

值类型方法接收者

值接受者:receiver是struct等值类型。

下面定义了值类型接受者 Person, 尝试使用 Person{}&Person{}去调用接受者函数。

  1. package main

  2. import "fmt"


  3. type Person struct {

  4. name string

  5. age int

  6. }


  7. func (p Person) say() {

  8. fmt.Printf("I (%p) ma %s, %d years old \n",&p, p.name,p.age)

  9. }


  10. func (p Person) older(){ // 值类型方法接受者:接受者是原类型值的副本

  11. p.age = p.age +1

  12. fmt.Printf("I (%p) am %s, %d years old\n", &p, p.name,p.age)

  13. }


  14. func main() {

  15. p1 := Person{name: "zhangsan", age: 20}

  16. p1.older()

  17. p1.say()

  18. fmt.Printf("I (%p) am %s, %d years old\n",&p1, p1.name,p1.age)


  19. p2 := &Person{ name: "sili", age: 20}

  20. p2.older() // 即使定义的是值类型接受者, 指针类型依旧可以使用,但我们传递进去的还是值类型的副本

  21. p2.say()

  22. fmt.Printf("I (%p) am %s, %d years old\n",p2, p2.name,p2.age)

  23. }

尝试改变p1=Person{},p2=&Person{}的字段值:

  1. I (0xc000098078) am zhangsan, 21 years old

  2. I (0xc000098090) ma zhangsan, 20 years old

  3. I (0xc000098060) am zhangsan, 20 years old

  4. I (0xc0000980c0) am sili, 21 years old

  5. I (0xc0000980d8) ma sili, 20 years old

  6. I (0xc0000980a8) am sili, 20 years old

p1=Person{} 未能修改原p1的字段值;p2=&Person{}也未能修改原p2的字段值。

  • 通过Person{}值去调用函数, 传入函数的是原值的副本, 这里通过第一行和第三行的 %p印证 (%p:输出地址值, 这两个非同一地址)。

  • 即使定义的是值类型接收者,指针类型依旧可以调用函数, 但是传递进去的还是值类型的副本

带来的效果是:对值类型接收者内的字段操作,并不影响原调用者。

指针类型接受者

方法接收者也可以定义在指针上,任何尝试对指针接收者的修改,会体现到调用者。

  1. package main


  2. import "fmt"


  3. type Person struct{

  4. name string

  5. age int

  6. }


  7. func (p Person) say(){

  8. fmt.Printf("I (%p) am %s, %d years old\n", &p, p.name,p.age)

  9. }


  10. func (p *Person) older(){ // 指针接受者,传递函数内部的是原类型值(指针), 函数内的操作会体现到原指针指向的空间

  11. p.age = p.age +1

  12. fmt.Printf("I (%p) am %s, %d years old\n", p, p.name,p.age)

  13. }


  14. func main() {

  15. p1 := Person{"zhangsan",20}

  16. p1.older() // 虽然定义的是指针接受者,但是值类型依旧可以使用,但是会隐式传入指针值

  17. p1.say()

  18. fmt.Printf("I (%p) am %s, %d years old\n", &p1, p1.name,p1.age)


  19. p2:= &Person{"sili",20}

  20. p2.older()

  21. p2.say()

  22. fmt.Printf("I (%p) am %s, %d years old\n", p2, p2.name,p2.age)

  23. }

尝试改变p1= Person{}, p2=&Person{}字段值

  1. I (0xc000098060) am zhangsan, 21 years old

  2. I (0xc000098078) am zhangsan, 21 years old

  3. I (0xc000098060) am zhangsan, 21 years old

  4. I (0xc000098090) am sili, 21 years old

  5. I (0xc0000980a8) am sili, 21 years old

  6. I (0xc000098090) am sili, 21 years old

p1=Person{} 成功修改字段值,p2=&Person{}也成功修改字段值。

  • 通过p1也可以调用指针函数接收者, 但是实际会隐式传递指针值。

  • 指针接收者,入参是原指针值,函数内的操作会体现到原调用者。

带来的效果:任何对指针接收者的修改会体现到 原调用者。

什么时候使用指针接收者

  1. 需要对接受者的变更能体现到原调用者

  2. 当struct占用很大内存,最好使用指针接受者,否则每次调用接受者函数 都会形成struct的大副本

golang方法的另外几种姿势

接上例子:

1. 将接收者函数当扩展函数

  1. Person.say(p1)

  2. (*Person).older(p2)

依旧是 值类型/指针类型方法接收者的效果:

  1. I (0xc0000040d8) am zhangsan, 21 years old

  2. I (0xc0000040a8) am sili, 22 years old

这种姿势相对于面向对象的接收者不常见。

 2. 形成golang 方法链条

  1. func (p Person) printName() Person{

  2. fmt.Printf("Name:%s", p.Name)

  3. return p

  4. } 

3. Non_struct类型golang方法

  1. type myFloat float64

  2. func (m myFloat) ceil() float64 {

  3. return math.Ceil(float64(m))

  4. }


以上是【有态度的马甲】记录的有关golang 方法接收者的全部用法,通过 %p,我们探究了值类型/指针接收者的调用原理, 辅助了记忆。


源码解读etcd heartbeat,election timeout之间的拉锯

利用etcd选举sdk实践master/slave故障转移

字节一面:go的协程比线程轻量,体现在哪?

自古以来,JSON序列化就是兵家必争之地

Gopher必读:HttpClient的两个坑位

HTTP1.1 Keep-Alive到底算不算长连接?

点“戳“在看

体现态度很有必要!


浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报