go json float32 转为_Go:一个隐藏在方法集和方法调用中且易被忽略的小细节

作为一个长期从事Go语言开发的程序猿,笔者不敢说自己是老油条但也勉强算一个小油条。然而就在今天,笔者研究TLS/SSL握手源码的时候,突然灵光一闪,想到了一个和自己认知不符的现象,于是赶紧写了一个例子验证一番,结果当头一棒直到码这篇文章时依旧懵逼。

话不多说,上锤!

3d1dfd05c0c38d2c041f9e650e9d13a0.png

不好意思,不是这个锤,是下面这个:

type set interface {
set1(s string)
set2(s string)
}
type test struct {
s string
}
func (t *test) set1(s string) {
t.s = s
}
func (t test) set2(s string) {
t.s = s
}
func main() {
var (
t1 test
t2 = new(test)
)
t1.set1("1")
fmt.Print(t1.s)
t1.set2("2")
fmt.Print(t1.s)
t2.set1("3")
fmt.Print(t2.s)
t2.set2("4")
fmt.Print(t2.s)
fmt.Print(" ")
_, ok1 := (interface{}(t1)).(set)
_, ok2 := (interface{}(t2)).(set)
fmt.Println(ok1, ok2)
}

正确答案笔者就不直接公布了,请各位读者耐心在后文寻找答案。

方法集

根据golang官方文档知道,一个类型有一个与之关联的方法集。接口类型的方法集是接口中定义的方法。

官方文档中特别提到,类型T的方法集包含用T声明为Receiver的所有方法,而指针类型*T的方法集包含用T和*T声明的所有方法。

此时,我们回到上面的例子可以很明显的知道下面这段代码输出为false true

_, ok1 := (interface{}(t1)).(set)
_, ok2 := (interface{}(t2)).(set)
fmt.Println(ok1, ok2)

T类型的方法集不包含*T类型的方法集,因此t1无法转为set接口类型。

事实上,根据这部分官方文档笔者更加疑惑了,因为上述例子可以正常运行,而且类型为test的变量调用了(*test).set1方法。抱着这样的疑惑笔者疯狂谷狗,最后在stackoverflow的指导下发现了这种情况和方法调用有关。

这里特别感谢一下谷狗和stackoverflow。

方法调用

方法调用笔者在这里仅说明和本篇相关的内容,其他细节相信各位读者都已经了然于胸。

下面,先看看官方文档原文:

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

简单来说,如果x可寻址,且&x的方法集包含m,则x.m()(&x).m的缩写。这样前面的例子能够正常运行也在情理之中了。

因此,前面例子的最终输出结果是:1133 false true

如果读者对结构体中的值未发生改变有疑惑,请参考笔者的这篇文章——为什么go中的receiver name不推荐使用this或者self。

你以为你都懂了

写完前面的方法集和方法调用笔者细细思考一番,确认没有其他遗漏的细节,于是放心的上了个厕所。结果厕所还没上完,立马想到一个问题(额外多扯一句,笔者经常在上厕所的时候找到灵感,这可能就是劳逸结合的最佳实践吧):

type los string

func (s los) p1() {
fmt.Println(s)
}

func (s *los) p2() {
fmt.Println(s)
}

func main() {
var s1 los = "1111"
var s2 *los = &s1
const s3 los = "3333"
s1.p1()
s1.p2()
s2.p1()
s2.p2()
s3.p1()
s3.p2()
}

如果你对上面的代码没有任何疑问且认为上述代码能够正常运行,那只能说明你对本文的阅读还不够认真。

我们先看看上述代码在vscode中的报错。

33e7e06619feb6562d83a3053270572e.png

前面介绍方法调用时,如果x可寻址,则x可以调用&x的类型的方法集。上述代码s3是常量,是不可以寻址的,因此无法调用(*los).p2方法。

以上,就是笔者曾经忽略的细节,现在回过头来看一看倒也充满了乐趣。

彩蛋

本篇是研究TLS/SSL握手流程的副产品,因为TLS/SSL握手流程笔者还在整理中,故这篇文章先行一步给个预告,下一期TLS/SSL握手流程敬请期待。

最后,衷心希望本文能够对各位读者有一定的帮助。

注:

  1. 写本文时, 笔者所用go版本为: go1.15.2

  2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/receiver/main.go

参考

https://golang.org/ref/spec#Method_sets

https://golang.org/ref/spec#Calls


推荐阅读

  • 卧槽!一道关于 json 和 slice 的题难倒了 80% 的人

福利 我为大家整理了一份 从入门到进阶的Go学习资料礼包 ,包含学习建议:入门看什么,进阶看什么。 关注公众号 「polarisxu」,回复  ebook  获取;还可以回复「进群」,和数万 Gopher 交流学习。

a5c46b499e8caa6c5d2c26e88782aa26.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值