Tony Bai · Go 语言第一课
Tony Bai
资深架构师,tonybai.com 博主
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 59 讲
开篇词 (1讲)
结束语 (1讲)
Tony Bai · Go 语言第一课
15
15
1.0x
00:00/00:00
登录|注册

25|方法:方法集合与如何选择receiver类型?

方法集合包含所有以*T为receiver参数类型的方法,以及所有以T为receiver参数类型的方法
使用dumpMethodSet函数验证结论
分析类型T和基于类型T定义的新类型S的方法集合
非接口类型的方法集合
用来判断某个类型是否实现了某个接口
方法集合决定了接口实现
如果T类型不需要实现某一接口,可以参考原则一和原则二来选择receiver参数的类型
如果T类型需要实现某个接口的全部方法,选择T作为receiver参数的类型
如果receiver参数类型的size较大,考虑性能开销时,选择*T作为receiver类型可能更好
一般情况下,选择T作为receiver参数的类型,以缩窄外部修改类型实例内部状态的“接触面”
如果要在方法中对receiver参数代表的类型实例进行修改,选择*T作为receiver参数的类型
思考题
方法集合
选择receiver参数类型的第三个原则
选择receiver参数类型的第二个原则
选择receiver参数类型的第一个原则
Go方法选择receiver参数类型的原则

该思维导图由 AI 生成,仅供参考

你好,我是 Tony Bai。
在上一节中我们开启了 Go 方法的学习,了解了 Go 语言中方法的组成、声明和实质。可以说,我们已经正式入门 Go 方法了。
入门 Go 方法后,和函数一样,我们要考虑如何进行方法设计的问题。由于在 Go 语言中,方法本质上就是函数,所以我们之前讲解的、关于函数设计的内容对方法也同样适用,比如错误处理设计、针对异常的处理策略、使用 defer 提升简洁性,等等。
但关于 Go 方法中独有的 receiver 组成部分,却没有现成的、可供我们参考的内容。而据我了解,初学者在学习 Go 方法时,最头疼的一个问题恰恰就是如何选择 receiver 参数的类型
那么,在这一讲中,我们就来学习一下不同 receiver 参数类型对 Go 方法的影响,以及我们选择 receiver 参数类型时的一些经验原则。

receiver 参数类型对 Go 方法的影响

要想为 receiver 参数选出合理的类型,我们先要了解不同的 receiver 参数类型会对 Go 方法产生怎样的影响。在上一节课中,我们分析了 Go 方法的本质,得出了“Go 方法实质上是以方法的 receiver 参数作为第一个参数的普通函数”的结论。
对于函数参数类型对函数的影响,我们是很熟悉的。那么我们能不能将方法等价转换为对应的函数,再通过分析 receiver 参数类型对函数的影响,从而间接得出它对 Go 方法的影响呢?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go方法的receiver参数类型选择对方法的影响是一个重要的设计考量。文章首先介绍了receiver参数类型对方法的影响,通过对不同类型的receiver参数类型的分析,得出了选择receiver参数类型的两个原则。第一个原则是,如果要在方法中对receiver参数代表的类型实例进行修改,应选择\*T类型作为receiver参数的类型;第二个原则是,如果不需要在方法中对类型实例进行修改,一般情况下选择T类型作为receiver参数的类型,但如果receiver参数类型的size较大,选择\*T类型可能更好。此外,文章还介绍了方法调用时receiver参数的自动转换,以及方法集合(Method Set)的概念。这些原则和概念为读者提供了在选择receiver参数类型时的指导原则和理论基础。 文章还介绍了方法集合的概念,方法集合是用来判断一个类型是否实现了某接口类型的唯一手段,可以说,“方法集合决定了接口实现”。方法集合的概念在Go语言中的主要用途,就是用来判断某个类型是否实现了某个接口。通过对方法集合的分析,文章提出了选择receiver参数类型的第三个原则,即根据T类型是否需要实现某个接口来选择receiver参数类型。如果T类型需要实现某个接口,就应该使用T作为receiver参数的类型来满足接口类型方法集合中的所有方法。 总的来说,文章通过对receiver参数类型选择的原则和方法集合的概念进行了深入的讲解,为读者提供了在选择receiver参数类型时的指导原则和理论基础。同时,文章还提出了选择receiver参数类型的第三个原则,即根据T类型是否需要实现某个接口来选择receiver参数类型。这些内容对于理解和设计Go方法的receiver参数类型具有重要的指导意义。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Tony Bai · Go 语言第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(27)

  • 最新
  • 精选
  • Geek_99b47c
    S 类型 和 *S 类型都没有包含方法,因为type S T 定义了一个新类型。 但是如果用 type S = T 则S和*S类型都包含两个方法。

    作者回复: 👍,抢答正确:)

    2021-12-10
    4
    77
  • 罗杰
    其实相比 Rust,Go 的糖更少,而且时而多,时而少,让开发者会很困惑,甚至前后矛盾。*T 和 T调用方法时编译器互相转换,哇,真贴心,真舒服。但是方法集合,又被 Go 反手打了一巴掌。的确解决了 C 语言的诸多问题,但对比 Rust 的一些处理方案,的确会让人不爽。

    作者回复: 是有点这种感觉哈。

    2021-12-10
    8
    23
  • liaomars
    老师: 如果 T 类型需要实现某个接口,那我们就要使用 T 作为 receiver 参数的类型,来满足接口类型方法集合中的所有方法。 这段描述感觉不对,根据上面举的例子来说,应该是使用 *T 作为 receiver参数的类型,来满足接口类型方法集合中的所有方法。

    作者回复: 我再举例说一下“如果 T 类型需要实现某个接口” 这句的含义。现在有一个接口类型I,一个自定义非接口类型T,这句的含义就是 我们希望 var i I var t T i = t 这段代码是ok的。即t可以赋值给i。 如果是*T实现了I,那么不能保证T也会实现I。所以我们在设计一个自定义类型T的方法时,考虑是否T需要实现某个接口。如果需要,方法receiver参数的类型应该是T。如果T不需要,那么用*T或T就都可以了。

    2021-12-10
    5
    14
  • Calvin
    老师,dumpMethodSet 函数只能统计导出方法的,有没有办法把非导出方法的也统计出来?

    作者回复: 能不能得到非导出方法的信息取决于reflect包是否提供对应的能力,要不留个作业:探索一下reflect包,看是否能得到非导出方法的列表。:)

    2021-12-11
    2
    4
  • 左耳朵东
    如果因为 T 类型需要实现某一接口而使用 T 作为 receiver 参数的类型,那如果我想把在方法里对 t 的修改反映到原 T 类型实例上,何做到呢?

    作者回复: 其实这里的“T类型是否要实现接口”的含义是是否存在将T类型值赋值给接口类型的情况。如果存在,则必须用T作为receiver,如果不存在,按原则1和2。我让编辑更新了原文,增加了说明。希望增加后大家理解起来更容易些。

    2021-12-11
    5
    3
  • 在下宝龙、
    S类型和*S类型都是 空方法,因为S是新的类型,它不能调用T的方法,必须显示转换之后才可以调用,所以本身的S或*S类型都不具有任何的方法

    作者回复: 👍

    2021-12-10
    3
  • 靠近我,温暖你
    S 类型 和 *S 类型都没有包含方法,因为type S T 定义了一个新类型。 但是如果用 type S = T 则S和*S类型都包含两个方法。

    作者回复: 👍

    2023-01-29归属地:河南
    2
  • 文经
    白老师,这课有两个疑问: 1. 当函数传递T类型的参数时,编译有办法判断对T类型是否有修改吗?如果没有修改的话,是否可以把参数转化为类似常量指针的方式引用,例如传递一个数组,如果没对数组修改 ,完全可以只传递一个指针。 2. 对于receiver选择的原则三:对T类型的限制比 *T 类型多,是不是因为有些时候一个T类型的对象无法获取它的地址,例如const 对象,但是一个T的指针类型,总是可以转换为 T类型的对象。

    作者回复: 1. 好像没有这个优化。 2. 我不能确定,但你的想法很新颖。

    2022-01-06
    2
  • 111
    测试一下 import ( "fmt" "reflect" ) type T struct{} func (T) M1() {} func (T) M2() {} type S T func dumpMethodSet(i interface{}) { dynTyp := reflect.TypeOf(i) if dynTyp == nil { fmt.Printf("there is no dynamic type\n") return } n := dynTyp.NumMethod() if n == 0 { fmt.Printf("%s's method set is empty!\n", dynTyp) return } fmt.Printf("%s's method set:\n", dynTyp) for j := 0; j < n; j++ { fmt.Println("-", dynTyp.Method(j).Name) } fmt.Printf("\n") } func main() { var s S dumpMethodSet(s) } ---------------------------------- S's method set is empty! *S's method set is empty! S 类型 和 *S 类型都没有包含方法 看来 S 是一个新类型,没有“继承”到T的方法

    作者回复: 正确✅

    2023-06-08归属地:北京
    1
  • 夏天
    方法接收者类型选择三个原则 1.如果需要修改接收者本身,传指针 *T 2.如果接受者本身较为复杂,传指针 *T,避免拷贝 3.*T 的方法集合是包含 T 的方法集合。*T 范围更大 go 文档不推荐混合使用,一般还是用 T* 吧。除非明确需要不改动 T 本身

    作者回复: 👍

    2022-11-21归属地:北京
    1
收起评论
显示
设置
留言
27
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部