go语言核心学习 14-15 - 程序员学点xx 55
Posted 运维开发时间
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go语言核心学习 14-15 - 程序员学点xx 55相关的知识,希望对你有一定的参考价值。
go_core -7-
<!--more-->
第 1 部分 承前
还是开篇舒服啊, 一下子三篇文章,完全不用顾及截稿的时间线。最近就很辛苦了,昨天也是10点半才完成。希望可以赶的前面一点,比如多一篇出来应急,估计只有周末可以实现了。
本日内容,接口和指针,极为精彩。
第 2 部分 正文
GO
接口
接口,接口类型,接口类型与其他数据类型不同,没法被实例化。
不能用 new 或 make 创建出一个接口类型的值,也没办法用字面量表示。如果没有任务数据类型作为其实现,接口不存在。
接口类型声明中的方法代表接口的方法集合,一个接口的方法集合就是它的全部特征。
type Pet interface {
SetName(name string)
Name() string
Category() string
}
声明了一个接口类型 Pet,它包含了 3 个方法定义,任何一个数据类型的方法集合中有这 3 个方法,就是 Pet 接口的实现类型。这种方式叫 “Duck typing”,“鸭子类型”。
怎样判定一个数据类型的某一个方法实现的就是某个接口类型中的某个方法呢?
方法签名一致,方法名称一样。
上节的示例说明,结构体类型 Cat 不是Pet接口的实现类型,但它的指针类型 *Cat 是。demo31.go
声明的类型Dog附带了 3 个方法,2个值方法,1个指针方法 SetName,所以 Dog 有2个方法, *Dog 有3个方法。Pet 接口需要 3个方法实现,所以只有 *Dog 可以实现,指针值赋给变量。
名词:
相对变量 pet
赋给 pet 的值 叫实际值(也叫动态值 &dog),该值的类型叫实际类型(动态类型)
静态类型 是 Pet 不会变
动态类型会变化,把 *Fish 赋给 pet ,其动态类型会变成 *Fish
给一个接口类型的变量赋予实际的值之前,它的动态类型是不存在
为接口变量赋值
demo32.go, 示例2:
dog2 := dog1
dog1.name = "monster"
dog1 名字会变,dog2不会,因为使用一个变量给另外一个变量赋值,那么真正赋给后者的,并不是前者持有的那个值,而是该值的一个副本。
示例3:
dog := Dog{"little pig"}
pet := &dog
dog.SetName("monster")
两边名字都会变,接口类型无法被值化。赋值之前,零值是nil。
pet的值与dog的值也是不同的,涉及结构,pet的值中包含了dog值的副本。
给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中。变量的值只是专用数据结构 iface 的一个实例,
iface的实例会包含两个指针,一个是指向类型信息的指针,另一个是指向动态值的指针。这里的类型信息是由另一个专用数据结构的实例承载的,其中包含了动态值的类型,以及使它实现了接口的方法和调用它们的途径等。晕了晕了,先记录。
接口值为 nil的情况
demo33.go , 声明了一个*Dog类型的变量dog1,没有初始化,变量值为nil,赋给 dog2,也是 nil。
Go 语言用 iface 包装 dog2 值的副本给 pet,pet 不是nil,被包装的 nil 只是pet值的一部分。此时,pet 的动态类型存在,是 *Dog。
把nil赋给了pet,但是pet的值却不是nil。
字面量nil表示的值叫做无类型的nil。赋给 pet时,识别出来需要赋予 pet 一个 *Dog 类型的 nil,然后Go 语言就会用一个iface的实例包装它,包装后的产物肯定就不是nil了。
让一个接口变量的值真正为nil?要么只声明它但不做初始化,要么直接把字面量nil赋给它。
接口之间组合
接口类型间的嵌入也被称为接口的组合。不会涉及方法间的“屏蔽”,有同名方法就无法通过编译。
鼓励小接口组合,以io.ReadWriteCloser接口为例,它是由io.Reader、io.Writer和io.Closer这三个接口组成的。
demo34.go 演示
GO
指针
回忆:
type Dog struct {
name string
}
func (dog *Dog) SetName(name string){
dog.name = name
}
结构体,方法绑定,指针方法。Dog类型,&dog 的结果就是该变量的值的指针值。
如果一个方法的接收者是*Dog类型的,那么该方法就是基本类型Dog的指针方法。其接收者是当前基本值的指针值。
可以通过指针值无缝地访问到基本值包含的任何字段,以及调用与之关联的任何方法。
其他指针:
uintptr类型
Pointer 类型
unsafe.Pointer可以表示任何指向可寻址的值的指针
不可寻址
常量的值
基本类型值的字面量
算术操作的结果
对各种字面量的索引表达式和切片表达式的结果
对字符串变量的索引表达式和切片表达式的结果
对字典变量的索引表达式的结果
函数和方法字面量,对其的调用表达式的结果
结构体字面量的字段值
类型转换表达式的结果
类型断言表达式的结果
接收表达式的结果
有很多看不懂是不是,就是说明很多概念没那么清楚。建议务必看练习代码 demo35.go 。
总体的感觉就是确认操作会有个值出来,但这个值不能寻址。另,对切片字面量的索引结果值可寻址。
然后是特征总结,不可变,临时结果,不安全。
不可变:常量、字符串值及衍生结果、函数及方法的字面量。
临时结果:计算结果、表达式求值、切片上的切片、字典索引
补充,
字面量不是变量,看赋值符号另一边。
不可寻址值,使用限制
无法使用取址操作符&获取其指针
demo36.go 示例,编写函数,接受参数,初始化 结构体,并返回。
New("little pig").SetName("monster")
调用New函数所得到的结果值属于临时结果,是不可寻址的。
同时 SetName 会转译,会报2个错,细节。
需要可寻址结果值的操作:
自增语句或自减语句的表达式
赋值操作符左边的表达式
range关键字左边的表达式
unsafe.Pointer
绕过 Go 语言的编译器和其他工具的检查,在内存中修改数据。
优先 API,reflect,go/ast 避免直接修改。
声明了一个Dog类型的变量dog,用 & 取出了它的指针值,赋给变量dogP。
使用了两层类型转换,先把指针值 dogP 转换成了一个 unsafe.Pointer 类型的值,然后把后者转换成了一个 uintptr 的值,并把它赋给了变量dogPtr 。
转换规则:
一个指针值可以被转换为一个unsafe.Pointer类型的值
一个uintptr类型的值也可以被转换为一个unsafe.Pointer类型的值
一个指针值无法被直接转换成一个uintptr类型的值
GO
问答
如果我们把一个值为nil的某个实现类型的变量赋给了接口变量,那么在这个接口变量上仍然可以调用该接口的方法吗?如果可以,有哪些注意事项?如果不可以,原因是什么?
答:可以调用。但是请注意,这个被调用的方法在此时所持有的接收者的值是nil。因此,如果该方法引用了其接收者的某个字段,那么就会引发 panic!
引用类型的值的指针值是有意义的吗?如果没有意义,为什么?如果有意义,意义在哪里?
答:从存储和传递的角度看,没有意义。因为引用类型的值已经相当于指向某个底层数据结构的指针了。当然,引用类型的值不只是指针那么简单。
第 3 部分 总结
今天很精彩,之前不明白的内容,重做一次弄明白了,开心。强烈建议多撸几次练习。
食用方法:
点左下角阅读原文
star 项目, 克隆一份
找个空闲时间,打开编辑器
把代码敲一遍,有问题给我留言(做就好了,有啥困难的)
ps. 源码克隆郝林老师的原版也可以, 不过我是每天敲完上传的,逐渐增加,完成压力小点。
对于 yann 的文章,相信很多朋友一定有话想说。麻烦先看下这篇自我解密,内容精彩,不容错过。
如果看完之后,还没有解决您的疑虑,请务必留言给我!
专题系列文章分享ing.
前篇
系列
知识的大楼:
[" 黄金广告位不租! "]
获取代码
收藏一下,以后再练
以上是关于go语言核心学习 14-15 - 程序员学点xx 55的主要内容,如果未能解决你的问题,请参考以下文章