Parts of A Tour Of Go
方法
Go
没有类。Go
可以为结构体类型定义方法。
方法就是一类带特殊的接收者参数的函数。
方法接收者在它自己的参数列表内,位于func
关键字和方法名之间。
1 | package main |
方法即函数
方法只是个带接收者参数的函数。
1 | package main |
为非结构体类型声明方法
接收者的类型定义和方法声明必须在同一个包内。
不能为内建类型声明方法。
1 | package main |
为指针接收者声明方法
对于某类型T
,接收者的类型可以使用*T
文法。*T
不能是像*int
这样的指针(不能为内建类型声明方法)。
指针接收者的方法可以修改接收者指向的值。
由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
若使用值接收者,那么方法会对原始值的副本进行操作。
修改原始值的方法要使用指针接收者。
1 | package main |
指针与函数
以指针作为接收者的方法 可以修改原值;
以非指针为接收者的方法 修改的是原值的副本。
1 | package main |
方法与指针重定向
含有指针参数的函数必须接受一个指针。
而以指针为接收者的方法被调用时,接收者既能是值又能是指针。
1 | package main |
接受一个值作为参数的函数必须接受一个指定类型的值。
而以值为接收者的方法被调用时,接收者既能为值又能为指针。
1 | package main |
选择值或指针作为方法的接收者
使用指针接收者的原因:
- 方法能够修改其接收者指向的值(原值)。
- 可以避免每次调用方法时复制该值(若值较大时,可避免额外的内存开销)。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。
1 | package main |
接口
接口类型
是由一组方法签名定义的集合。
接口类型的值可以是任何实现了这些方法的值。
1 | package main |
接口与隐式实现
类型通过实现一个接口的所有方法来实现该接口。
隐式接口从接口的实现中解耦了定义,这样的接口可以出现在任何包中,无需提前准备。
也就无需在每个实现上增加新的接口名称,同时也鼓励粒度明确的接口定义。
接口值
接口值可以看做包含值和具体类型的元组。
接口值保存了一个具体底层类型的具体值。
接口值调用方法时会执行其底层类型的同名方法。
1 | package main |
底层值为nil
的接口值
即便接口内的具体值为nil
,方法仍然会被nil
接收者调用。
在一些语言中,这会触发一个空指针异常,Go
中通常会写一些方法来优雅的处理它。
注意:保存了nil
具体值的接口其自身并不为nil
1 | package main |
nil
接口值
nil
接口值既不保存值也不保存具体的类型。
为nil
接口调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个具体方法的类型。
1 | // build通过,run报错 |
空接口
指定了零个方法的接口值被称为空接口:interface{}
。
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法)
空接口被用来处理未知类型的值。
1 | package main |
类型断言
类型断言
提供了访问接口值底层具体值的方式。
1 | package main |
类型选择
类型选择
是一种顺序从几个类型断言中选择分支的结构。
类型选择与一般的switch
语句相似,不过类型选择中的case
为为类型(而非值),它们针对给定接口值所存储的值的类型进行比较。
1 | package main |
Stringer
fmt
包中定义的Stringer
是最普遍的接口之一。
1 | type Stringer interface { |
Stringer
是一个可以用字符串描述自己的类型。fmt
包(还有很多包)都通过此接口来打印值。
1 | package main |
练习: Stringer
通过让IPAddr
类型实现fmt.Stringer
来打印点号分隔地址。
例如,IPAddr{127, 0, 0, 1}
应当打印为127.0.0.1
。
1 | package main |
错误
Go
使用error
值来表示错误状态。
与fmt.Stringer
类似,error
类型是一个内建接口。
1 | type error interface { |
与fmt.Stringer
类型,fmt
包在打印值时也会满足error
。
通常函数会返回一个error
值,调用该函数的代码应当判断这个错误是否等于nil
来进行错误处理。error
等于nil
时表示成功,非nil
的error
表示失败。
1 | package main |
练习: 错误
1 | package main |
Reader
io
包指定了io.Reader
接口,它表示从数据流的末尾进行读取。Go
标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等。io.Reader
接口有一个Read
方法:
1 | func (T) Read(b []byte) (n int, err error) |
Read
用数据填充给定的字节切片并返回填充的字节数和错误值。
在遇到数据流的结尾时,它会返回一个io.EOF
错误。
1 | // 示例代码创建了一个 strings.Reader |
练习: Reader
实现一个Reader
类型,它产生一个ASCII
字符A
的无限流。
1 | package main |
练习: rot13Reader
有种常见的模式是一个io.Reader
包装另一个io.Reader
,然后通过某种方式修改其数据流。
例如,gzip.NewReader
函数接受一个io.Reader
(已压缩的数据流)并返回一个同样实现了io.Reader
的*gzip.Reader
(解压后的数据流)。
编写一个实现了io.Reader
并从另一个io.Reader
中读取数据的rot13Reader
,通过应用rot13
代换密码对数据流进行修改。rot13Reader
类型已经提供。
实现Read
方法以满足io.Reader
。
1 | package main |
图像
image
包定义了Image
接口:
1 | package image |
注意: Bounds
方法的返回值Rectangle
实际上是一个image.Rectangle
,它在image
包中声明。
1 | package main |
练习: 图像
1 | package main |