携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 11 天,点击查看活动详情
7. 接口类型
接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例
io.Writer
类型
io.Writer
类型是用得最广泛的接口之一,因为它提供了所有类型的写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP
客户端,压缩工具,哈希等等。io
包中定义了很多其它有用的接口类型。Reader
可以代表任意可以读取bytes
的类型,Closer
可以是任意可以关闭的值,例如一个文件或是网络链接。
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
flag
类型
需要实现 flag.Value
接口的类型
package flag
// Value is the interface to the value stored in a flag.
type Value interface {
String() string
Set(string) error
}
8. 接口值
概念上讲一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。
定义了变量w
,类型和值都是 nil
var w io.Writer
w.Write([]byte("hello")) // panic: nil pointer dereference
将一个os.File
类型的值赋给变量 w
,这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)
是等价的。这个接口值的动态类型被设为os.File
指针的类型描述符,它的动态值持有os.Stdout
的拷贝
w = os.Stdout
调用一个包含*os.File类型指针的接口值的Write方法,使得(*os.File).Write方法被调用。这个调用输出“hello”
w.Write([]byte("hello")) // "hello"
效果和下面这个直接调用一样:
os.Stdout.Write([]byte("hello")) // "hello"
给接口值赋了一个bytes.Buffer
类型的值,现在动态类型是bytes.Buffer
并且动态值是一个指向新分配的缓冲区的指针
w = new(bytes.Buffer)
Write方法的调用也使用了和之前一样的机制:
w.Write([]byte("hello")) // writes "hello" to the bytes.Buffers
这次类型描述符是*bytes.Buffer,所以调用了(*bytes.Buffer).Write方法,并且接收者是该缓冲区的地址。这个调用把字符串“hello”添加到缓冲区中。
将nil
赋给了接口值,这个重置将它所有的部分都设为nil
值,把变量w
恢复到和它之前定义时相同的状态
将nil赋给了接口值
9. 接口嵌套
接口与接口间可以通过嵌套创造出新的接口。
例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly
接口,创建一个游泳接口Swim
,飞鱼接口有这两个接口组成。
飞 Flyer 接口
type Flyer interface {
fly()
}
创建 Swimmer 接口
type Swimmer interface {
swim()
}
组合一个接口 FlyFish
type FlyFish interface {
Flyer
Swimmer
}
创建一个结构体 Fish
type Fish struct {}
实现这个组合接口
func (fish Fish) fly() {
fmt.Println("fly...")
}
func (fish Fish) swim() {
fmt.Println("swim...")
}
嵌套得到的接口的使用与普通接口一样。
例子代码:
func main() {
var ff FlyFish
ff = Fish{}
ff.fly()
ff.swim()
}
运行结果
fly...
swim...
10. 空接口
10.1 定义
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
10.2 应用
空接口作为函数的形参
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
空接口作为 map
的值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "程序员小乔"
studentInfo["age"] = 18
studentInfo["married"] = false
11. 类型断言
具体类型的类型断言从它的操作对象中获得具体的值。如果检查失败,接下来这个操作会抛出 panic
var w io.Writer
w = os.Stdout
f := w.(*os.File)
c := w.(*bytes.Buffer)
对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保留了接口值内部的动态类型和值的部分。
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic
下一篇我们继续探索 Go
语言更多知识。敬请期待!