目前为止,我们已经学过很多类型的值了,比如 int 值,float 值,后面又学过函数值,方法值。到了接口这里也不例外,我们需要学习接口值(Interface value)。
之所以单独拿出来讲,是因为接口值也有它与众不同的地方。
1. 接口值(Interface Value)
从概念上讲,接口值由两部分组成:
- 具体类型 (Type)
- 该类型对应的值 (Value)
之所以加一个修饰『从概念上讲』,是因为 go 内部具体实现不一定会严格按照这种方式来做,比如可能会扩充额外的字段。
这可能会让你一脸懵逼,看一张图举几个例子你就明白了。
一个被初始化为 0 值的接口,就像图 1 中那样,type 和 value 部分都是 nil.
图1 接口值构成
2. 示例
下面这个示例展示了一个空接口被赋值的情况:
var e interface{}
fmt.Println(e, e == nil) // Output: <nil> true
e = 5
fmt.Println(e, e == nil) // Output: 5 false
var x *int
e = x
fmt.Println(e, e == nil) // <nil> false
上面三种输出结果,我们依次分析。
接口值为 nil
一个接口值为 nil,需要接口值的两个组成部分都为 nil,即 type 为 nil,value 也为 nil。对于一个使用
var e interface{}
声明的空接口来说,这两个部分都被初始化为了 0 值,因此打印的时候,e 为<nil>
,且e == nil
比较结果也为真。e = 5
此时为接口赋值为 5,e 的两个组成部分会变成下面这样:
图2 接口值
因此在打印的时候,e 输出的值为 5,而 e == nil 输出的值为 false.
e = x
这算是一种比较特殊的情况。x 是一个类型为 *int
的指针,x 的值为 nil
。将 x 赋值给 e 后,e 的两个组成部分变成下面这样:
图3 接口值
最后打印的结果非常奇怪,e 输出了 <nil>
而 e == nil
却输出了 false
. 其实这非常容易解释,刚刚我们就说过了,只有接口值的两部分都为 nil,接口值才是 nil。
那为什么 e 输出的是 nil
呢?我们只能说,println
只能打印出 e 的 value 部分,而无法打印出 e 的 type 部分。如果打印 type 部分,你可以像下面这样做:
fmt.Printf("%v %T %v\n", e, e, e == nil) // Output: <nil> *int false
3. 接口值比较
接口值之间可以比较相等,接口值与普通值也可以进行比较。
接口值比较会有两步:
- 比较 type 部分
- 比较 value 部分
如果 type 不同,== 操作会返回 false. 如果 type 相等,再比较 value,如果 value 也相等,那就返回 true 了。
然而,坑点在于 value 部分。如果 value 本身是可以比较的,那么接口值就可以比较。如果 value 不可以比较,会报 panic 错误。
比如 slice 类型,只能和 nil 值进行比较,而 slice 之间是不可比较的。下面的比较会报错:
// 编译期就会报错 invalid operation: x == y (slice can only be compared to nil)
x := []int{1, 2, 3}
y := []int{1, 2, 3}
fmt.Println(x == y)
如果将 x 和 y 的值赋值给接口,再比较 2 个接口的值:
// 运行时报错 panic: runtime error: comparing uncomparable type []int
x := []int{1, 2, 3}
y := []int{1, 2, 3}
var xx interface{} = x
var yy interface{} = y
fmt.Println(xx == yy)
这样一来,接口值的比较是一件相当危险的操作,因为错误会发生在运行期。
我们再来看一个例子:
package main
import "fmt"
func main() {
var x interface{} = []int{1, 2, 3}
var y interface{} = [3]int{1, 2, 3}
fmt.Println(x == y) // Output: false
}
上面的情况很惊险,没有报 panic 错误,竟然通过了。不过一得『归功于』 x 和 y 的 type 不同,因此返回了 false. 这是相当危险的操作。
4. 总结
- 掌握接口值的构成
- 掌握接口值比较
本文中的例子使用的接口都是空接口,你可以换一个非空接口试试。