Day 3:
Go没有类的概念,但是可以为结构体类型定以方法
方法就是有特殊接收器参数的函数
方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间
此例中,Abs方法有一个Vertex类型的接收器v
方法仅仅是一个拥有接收器参数的函数
这里的代码实现了一个与方法功能一致的普通函数
也可以为非结构体类型声明方法
此例中我们看到一个拥有Abs方法的数值类型MyFloat
注意:只能在类型与方法在同一个包中的情况下声明方法,而不能在两者不在同一个包的情况下声明方法甚至内置类型(如int)也不行
package main
import (
"fmt"
"math"
)
func (f float64) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := float64(-math.Sqrt2)
fmt.Println(f.Abs())
}
./prog.go:8:6: cannot define new methods on non-local type float64
./prog.go:17:15: f.Abs undefined (type float64 has no field or method Abs)
可以使用指针接收器声明方法
对于某些类型T,接收器的类型可以使用T文法(类型T本身不能是指针例如int)
拥有指针接收器的方法可以修改指针接收器指向的值。由于方法经常需要修改它们的接收器,所以指针接收器比值接收器更常见。
如果把Scale方法的指针接收器换成和Abs方法一样的值接收器,则接收到的v仅是原始值的拷贝,因而无法实现对其修改。
功能不变,把之前方法重写成函数
函数中如果有指针参数则必须要传入指针,而指针作为接收器的方法被调用时,接收器可以是指针也可以是值。
例如v.Scale(5),假设v是一个值而不是指针,指针作为接收器的方法Scale会自动被调用,这是因为当Scale方法有指针接收器时,Go会将v.Scale(5)解释为(&v).Scale(5)
反过来如果函数需要值参数,则必须要传入对应类型的值,而值作为接收器的方法被调用时,接收器可以是指针也可以是值
本例中p.Abs()被解释为(*p).Abs()
更多的情况是使用指针接收器而不是值接收器,主要原因有两点:
- 方法可以修改接收器所指向的值
- 避免每次方法调用时拷贝值所带来的性能损耗,尤其是接收器为比较大的结构体时
本例中Scale和Abs方法都有指针接收器,但是Abs方法不需要修改接收器的值
通常,所有给定类型的方法都需要有值或者指针接收器,但不能同时存在
接口类型 是由一组方法签名定义的集合。
接口类型的变量可以保存任何实现了这些方法的值。
这里需要一些背景知识,否则不太好理解https://blog.youkuaiyun.com/MacwinWin/article/details/112789278
一个类通过实现接口的所有方法实现(implement)该接口,没有专门的显示声明,就没有“implements”关键字
隐式接口使接口的定义和实现解耦,接口可以在没有预先安排的情况下出现在任何包中。由于这种类似于“鸭子类型”的特性,使得不必为每个类定义接口
在内部,接口值可以被理解为值和对应类型的元组
接口值保存了一个具体底层类型的值
在接口上执行某方法会调用其底层类型的同名方法
即便接口的具体值为nil,仍然会调用nil接收器的方法
在某些语言中这会出发空指针异常,但在 Go 中通常会写一些方法来优雅地处理它
注意:保存nil值的接口本身是非nil的
nil接口的值既没有值也没有具体类型
调用nil接口的方法会导致运行时错误,这是因为该接口内部没有指明调用哪个具体方法的类型
指定了0个方法的接口被称为空接口
空接口可保存任何类型的值(因为每个类型都至少实现了0个方法)
空接口被用来处理未知类型的值
类型断言提供了访问接口值底层值的方法
t := i.(T)
这条语句断言接口值i保存具体方法T并将底层类型为T的值赋给变量t
如果i没有保存类型为T的值,则语句会触发一个恐慌
为了测试一个接口值是否保存一个特定的类型,将类型断言可返回两个值:
- 底层值
- 是否断言成功的布尔值
t, ok := i.(T)
如果i保存有T类型的值,则t将为底层值,ok将为true
反之,ok将为false,t将为类型T的0值,恐慌不会被触发
注意这里和测试映射中是否存在某键的语法很像
elem, ok = m[key]
类型选择是一种允许按一定顺序从几个断言中选择分支的结构
类型选择类似常规的switch语句,但是类型选择中的case为类型而非值,并且那些值被与提供的接口值所保存的值的类型进行比较
类型选择中的声明与类型断言的语法一致,但是类型T被关键字type替代
switch语句测试是否接口值i保存类型T或S的值,在每个case语句中,变量v将会是类型T或S并且保存i的值;在default case语句中,变量 v 与i的接口类型和值相同
fmt包中定义的Stringer接口是最普遍的接口之一
Stringer类型可以描述它自身为一个string,fmt包和其他许多包依赖这个接口打印值
练习比较简单,就是重新实现fmt.Stringer接口中的String方法,实现格式化打印字符串
Go程序使用error值来表示错误状态
error类型是一个内部接口,和fmt.Stringer类似
和fmt.Stringer类似,fmt包在打印值的时候也会寻找error接口
通常函数会返回error值,调用的代码需要通过判断error是否为nil来处理error
error值为nil意为成功,非nil的error值意为失败
比较简单的一个练习,但其中的问题比较让人费解:
在Error方法内调用fmt.Sprint(e)将会导致程序进入无限循环,而避免出现这种情况只需要转换一下e,这是为什么?
比较准确的解释是fmt.Sprintf(e)语句会调用e.Error()方法以将e转为String类型,导致函数自己调用自己的死循环,转为float64后,就没有Error()、String()方法,自然也就不会进入自己调用自己的死循环中了
参考https://stackoverflow.com/a/27475316
io包提供了io.Reader接口,表示从流数据的末尾进行读取
Go的标准库包含许多该接口的实现,包括文件、网络连接、压缩、加密等等
Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。
示例代码创建了一个 strings.Reader 并以每次 8 字节的速度读取它的输出。
说实话没看懂题的意思,代码是抄的
首先代码是抄的,然后看懂了
第26行创建了类型为strings.NewReader的变量s
第27行创建了类型为rot13Reader的结构体变量r,由于strings.NewReader实现了io.Reader接口,所以能将变量s赋给io.Reader类型的r
第29行将os.Stdout和&r传入io.Copy函数中,由于第13行中Read方法为指针接收器,所以此处必须为&r而不能是r。没有看源码,但可以肯定的是io.Copy函数肯定调用了r.Read方法
第14行使用rot.r即s的Read函数读取"Lbh penpxrq gur pbqr!"到二进制切片b中;
第15-21行为rot13算法的实现部分,解密上面的文本
关于io.Copy可以参考源码:https://github.com/golang/go/blob/ecf4ebf10054f70e51a0ce759b2ae91aa4febd1a/src/io/io.go#L381