方法
Go 没有类。不过你可以为结构体类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
1 | type v struct { |
记住:方法只是个带接收者参数的函数。
你也可以为非结构体类型声明方法。
1 | type MyFloat float64 |
指针接收者
你可以为指针接收者声明方法。
这意味着对于某类型 T,接收者的类型可以用 *T 的文法。
指针接收者的方法可以修改接收者指向的值
1 | package main |
方法与指针重定向
带指针参数的函数必须接受一个指针
以指针为接收者的方法被调用时,接收者既能为值又能为指针
接受一个值作为参数的函数必须接受一个指定类型的值
而以值为接收者的方法被调用时,接收者既能为值又能为指针
选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
接口
接口类型 是有一组方法签名定义的集合
接口类型的变量可以保存任何实现了这些方法的值
接口与隐式实现
类型通过实现一个接口的所有方法来实现该接口
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义
在内部,接口值可以看做包含值和具体类型的元组:
(value, type)
接口值保存了一个具体底层类型的具体值
接口值调用方法时会执行其底层类型的同名方法
底层值为 nil 的接口值
即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。
在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。
注意: 保存了 nil 具体值的接口其自身并不为 nil。
空接口
指定了零个方法的接口值被称为 空接口:
1 | interface{} |
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。
类型断言
类型断言提供了访问接口值底层具体值的方式
1 | t := i.(T) |
类型选择
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
1 | switch v := i.(type){ //注意:type为关键字 |
类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type。
此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 T 或 S 的情况下,变量 v 会分别按 T 或 S 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 v 与 i 的接口类型和值相同。
练习:Stringer
通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。
例如,IPAddr{1, 2, 3, 4} 应当打印为 “1.2.3.4”。
1 | package main |
错误
Go 程序使用 error 值来表示错误状态。
与 fmt.Stringer 类似,error 类型是一个内建接口:
1 | type error interface { |
(与 fmt.Stringer 类似,fmt 包在打印值时也会满足 error。)
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。
1 | i, err := strconv.Atoi("42") |
error 为 nil 时表示成功;非 nil 的 error 表示失败。
Reader
io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。
Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等。
io.Reader 接口有一个 Read 方法:
func (T) Read(b []byte) (n int, err error)
Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。
示例代码创建了一个 strings.Reader 并以每次 8 字节的速度读取它的输出。