接口概念
一个接口的值(简称接口值)是由一个具体类型
和具体类型的值
两部分组成的。这两部分分别称为接口的动态类型
和动态值
。
非空接口的底层数据结构是iface
,其代码如下(代码位于src/runtime/runtime2.go中):
type iface struct{
tab *itab //itab存放类型及方法指针信息
data unsafe.Pointer //数据信息
}
iface
的结构很简单,有两个指针类型字段。
- itab:用来存放接口自身类型和绑定的实例类型及实例相关的函数指针。
- 数据指针data:指向接口绑定的实例的副本,接口的初始化也是一种值拷贝。
空接口的底层数据结构也很简单,代码如下:
type eface struct{
_type *_type
data unsafe.Pointer
}
从eface的数据结构可以看出,空接口不是真的为空,其保留了具体实例的类型和值拷贝,即便存放的具体类型是空的,空接口也不是空的。
可以使用类型断言来判断接口中的值,其语法格式:
x.(T)
// x表示为 interface{}的变量
// T表示断言x可能的类型
该语法返回两个参数,第一个参数是x
转化为T
类型后的变量,第二个值是一个布尔值,若为true
则表示断言成功,为false
则表示断言失败。
x.(T)
示例:
func main() {
var x interface{}
x = 666
value,ok := x.(int)
if ok == true{
fmt.Println("断言成功,值为:",value)
}else{
fmt.Println("断言失败,x不是int类型")
}
}
// 运行结果:
// 断言成功,值为:666
而 x.(type)
这种方式的类型断言,就只能和 switch
搭配使用,因为它需要和多种类型比较判断,以确定其具体类型。
x.(type)
示例:
func checkType(x interface{}){
switch t := x.(type) {
case string:
fmt.Println("string类型: ",t)
case int:
fmt.Println("int类型: ",t)
case bool:
fmt.Println("bool类型: ",t)
default:
fmt.Println("未知类型")
}
}
func main() {
var x interface{}
x = 666
checkType(x)
x = "test"
checkType(x)
}
//运行结果:
// int类型: 666
// string类型: test
注意:
x.(type)
, x.(T)
中的 x
都必须是接口类型,否则会编译失败。
例如:
func main() {
var x int //x为int类型
x = 666
v,ok := x.(int)//编译失败,提示invalid type assertion: x.(int) (non-interface type int on left)
if ok == true{
fmt.Println("int ",v)
}else{
fmt.Println("unknow")
}
}
但此时x作为checkType
函数参数时,编译通过。
因为x interface{}
这个实例承载了x int
类型的值。
func checkType(x interface{}){
switch t := x.(type) {
case string:
fmt.Println("string类型: ",t)
case int:
fmt.Println("int类型: ",t)
case bool:
fmt.Println("bool类型: ",t)
default:
fmt.Println("未知类型")
}
}
func main() {
var x int //x为int类型
x = 666
checkType(x)
}
//运行结果:
// int类型: 666