一个接口的值由两个部分组成,一个是具体的类型,另一个是那个类型的值,我们称他们两者分别为动态类型和动态值。
类型是编译期的概念;因此一个类型不是一个值。
类型的名称和方法我们都称他们为类型描述符
我们看下面的代码:
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
var w io.Writer
代表着这不是一个空接口,对于任何实现了write
方法的类型都可以赋值给w
w可以存储任何实现了io.Writer接口的具体类型的值
我们来说一下空接口
var a = interface{}
记住,变量的type和value肯定是具体化的,都是nil
一个接口值的描述需要关注它的动态类型
我们说这个接口值是一个空的接口值
可以使用w == nil
来判断接口值是否为空
调用一个空接口值上的任意方法都会产生panic(因为它还没有实现嘛)
w.Write([]byte("hello"))//panic:nil pointer
接着,我们传入了w = os.Stdout
一个实例作为一个参数
os.Stdout
是*os.File
类型的实例
其实我一开始不是很能理解为什么传的是一个实例
首先,接口变量存储的是一个具体实例值,而不是类型本身
其次,当我声明一个接口变量的时候,我实际上是在声明一个可以持有任何实现了该接口的具体类型的变量
现在我们可以调用方法了
w.Write([]byte("hello")
这个语法实际上是在编译器的时候,我们并不知道接口值的动态类型是什么,所以一个接口上的调用必须使用动态分配。
也就是直接写这个被赋值的w
而不是直接调用:
os.Stdout.Write([]byte("hello"))
我们会在后期间接调用这个地址,os.Stdout
w = new(bytes.Buffer)
这个语句给接口值附上了一个*bytes.Buffer类型的值
这次类型描述符是bytes.Buffer,所以最终调用的是(bytes.Buffer).Write方法,并且接收者是该缓冲区的地址
最后,第四个语句将nil赋给了接口值:
w = nil
在接口值和nil进行比较的时候,需要动态类型和接口值都等于nil
我们再继续细说比较的问题: 如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),如果对他们进行比较就会panic
var x interface{} = []int{1, 2, 3}
fmt.Println(x == x) // panic: comparing uncomparable type []int
只能比较你非常确定他们的动态值是可比较类型的接口值