GoLang之T和*T的方法集是啥关系

GoLang之T和*T的方法集是啥关系

1.深究

1.1两大问题

基于我们之前介绍过的类型元数据相关知识,T和*T是两种类型,分别有着自己的类型元数据,一些元数据相关知识t he兴替是两种类型,分别有着自己的类型元数据,而根据自定义类型的类型元数据可以找得到该类型关联的方法列表。

在这里插入图片描述

既然T和*T各有各的方法集,那为什么还要限制T和*T不能定义同名方法,

在这里插入图片描述

又怎么会有"*T的方法集包含T的方法集合“这种说法?

在这里插入图片描述

1.2T的方法集:接收者为T类型的方法

接下来咱们就来梳理下这两个问题。

首先可以确定的是,T的方法集里全部都是有明确定义的接收者为T类型的方法,

在这里插入图片描述

1.3T的方法集:接收者为T类型的方法+包装方法

而*T的方法集里除了有明确定义的接收者为*T的方法以外,还会有编译器生成的一些”包装方法“,这些包装方法是对接收者为T类型的同名方法的“包装”,

在这里插入图片描述

1.4包装方法语法糖

为什么编辑器要为接受者为T的方法包装一个接收者为*T的同名方法呢?

在这里插入图片描述

这里首先要明确一点,“通过*T类型的变量直接调用T类型接收者的方法”只是一种语法糖,经验证:这种调用方式,编译器会在调入端进行指针解引用,并不会用到这里的包装方法。

在这里插入图片描述

1.5解释两大问题

实际上编译器生成包装方法主要是为了支持接口,还记得接口的数据结构吗?。涉及到方法,自然是非空接口。它只包含两个指针,一个和类型元数据相关,一个和接口装载的数据相关。

在这里插入图片描述

虽然有数据指针,但却不能像这里的语法糖那样通过指针解引用来调用值接收者的方法。
至于原因嘛,你想,方法的接收者是方法调用时隐含的第一个参数,go语言中函数参数是通过栈来传递的,如果参数是指针类型,那就很好实现,平台确定了,指针大小就确定了。但如果要解引用为值类型,就要有明确的类型信息,编译器才能确定这个参数要在栈上占用多大的空间。
而对于接口,编译阶段并不能确定他会装载哪一类数据,所以编译器也不能生成对应的指令来解引用
总而言之,接口不能直接使用接收者为值类型的方法。

在这里插入图片描述

面对这个问题,编译器选择为值接收者的方法生成指针接收者的同名包装方法这一解决方案。也正因为如此,如果我们再给*T和T定义同名方法,就有可能和编译器生成的包装方法发生冲突,所以go语言干脆不允许为T和*T定义同名方法。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.6分析可执行文件

至于说"*T的方法集"及包含"T的方法集的所有方法"。这种说法可以这样理解。虽然编译器会对所有接收者为T的方法生成接收者为*T的包装方法,但是链接器会把程序中确定不会用到的方法都裁剪掉

在这里插入图片描述

所以,如果去分析可执行文件的话,就会发现,不只是这些包装方法,就连我们明确定义的方法也不一定会存在于可执行文件中。不过一定要记得从可执行文件去分析,不能通过反射在程序中验证,因为反射的实现也是基于接口,通过反射来验证,会被链接器认为用到了这个方法,从而把它保留下来,这就测不准了。

在这里插入图片描述
在这里插入图片描述

2.附

2.1接口传值,调用值接收者,不改变原来的值

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
}

func (c Stu) hello() {
	fmt.Println("11111")
}
func main() {
	var IfTs Iface
	var stu Stu
	IfTs = stu
	IfTs.hello() //"11111"
}

不会改变到的值

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
	a int
}

func (c Stu) hello() {
	c.a = 10000
}
func main() {
	var IfTs Iface
	var stu Stu = Stu{2}
	IfTs = stu
	IfTs.hello()
	fmt.Println(stu) //{2}
}

2.2接口传指针,调用指针接收者,改变到原来的值

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
}

func (c *Stu) hello() {
	fmt.Println("11111")
}
func main() {
	var IfTs Iface
	var stu Stu
	IfTs = &stu
	IfTs.hello()
}

改变到原来的值

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
	a int
}

func (c *Stu) hello() {
	c.a = 10000
}
func main() {
	var IfTs Iface
	var stu Stu = Stu{2}
	IfTs = &stu
	IfTs.hello()
	fmt.Println(stu) //{10000}
}

2.3接口传指针,调用值接收者,不改变到原来的值(success)

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
}

func (c Stu) hello() {
	fmt.Println("11111111")
}
func main() {
	var IfTs Iface
	var stu Stu
	IfTs = &stu
	IfTs.hello() //11111111

}

不改变到原来的值

package main

import "fmt"

type Iface interface {
	hello()
}
type Stu struct {
	a int
}

func (c Stu) hello() {
	c.a = 10000
}
func main() {
	var IfTs Iface
	var stu Stu = Stu{2}
	IfTs = &stu
	IfTs.hello()
	fmt.Println(stu) //{2}
}

2.4接口传值,调用指针接收者(error)

package main

type A interface {
	hello()
}
type B struct {
}

func (c *B) hello() {
	panic("implement me")
}
func main() {
	var a A
	var b B
	a = b //在此编译报错
	a.hello()
}


在这里插入图片描述

2.5附

1:

package main

import "fmt"

type T struct {
}

func (t T) A() {
}

func (pt *T) A() {
}

在这里插入图片描述

2:

package main

type I interface {
	A()
}

func main() {
	var i I
	i.A() //是可以编译过的,但是运行不通过,引发panic
}

2.6总结

那为什么第三种情况可以正常运行呢?通过*T类型的变量调用T类型的接收者的方法,只是一种语法糖,编译器会在调用端进行指针解引用。 那为什么第四种情况会err呢?因为*T和T是两种不同的类型,这里没有语法糖,T类型没有实现接口的方法,自然会err咯。无法将’stu’ (类型Stu)用作类型Iface类型,未实现’Iface’

总结:
因为通过 *T 类型的变量调用T类型的接收者的方法,是一种语法糖,所以可以简单的理解为 *T 的方法集包括了所有的T方法集,这也就是为什么会有人说,只要接收者里面有一个是指针接收者,那就把所有的方法接收者都改为指针接收者的原因了。不过需要注意的是T和 *T 是两种不同的类型!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值