类型断言
在Go语言中,类型断言是一种检查并确认接口变量的底层具体类型的操作。类型断言的语法格式如下:
这里,x
表示一个接口的变量,而T
表示一个类型。类型断言返回两个值:value
和 ok
。
value
是变量的底层值,而ok
是一个布尔值,当类型断言正确时为true
,否则为false
。
如果断言是正确的,那么value
将是x
转换为类型T
的值,如果断言不正确,那么value
将是类型T
的零值,ok
将是false
。
举个栗子🌰:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 接口
type BankAccount interface {
printBalance()
}
// 实现类
type CCGBank struct {
Name string
IdCard uint64
password string
Balance float64
}
// 实现方法
func (c CCGBank) printBalance() {
fmt.Println(c.Balance)
}
func main(){
var bk BankAccount = new(CCGBank)
bk.printBalance()
// 类型断言
bank, ok := bk.(CCGBank)
if ok {
fmt.Println("this is CCG", bank)
}
}
|
空接口
在Go语言中,空接口(interface{}
)是一个特殊的类型,表示没有任何方法的接口。由于Go语言的接口是隐式实现的,任何类型都至少实现了0个方法,因此任何类型都可以看作是空接口类型。这使得空接口可以存储任何类型的值,类似于其他语言中的 Object
类型。
这是空接口的定义:
举个栗子🌰:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 空接口
var any interface{}
// 数值
any = 42
fmt.Println(any)
// 字符串
any = "hello world"
fmt.Println(any)
// 切片
any = []int{1, 2, 3, 4, 5}
fmt.Println(any)
// 切片接口,可以存储任意类型的值
var anySlice []interface{}
anySlice = []interface{}{1, "hello", []int{1, 2, 3}}
for _, val := range anySlice {
fmt.Print(val)
}
|
第二枚🌰:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 空接口作为函数参数,接受任意类型的值
func printData(v interface{}) {
switch value := v.(type) {
case int:
fmt.Println("value is int", value)
case string:
fmt.Println("value is string", value)
default:
fmt.Println("value is unknown", value)
}
}
func main(){
printData(1)
printData("abc")
printData([]int{1, 2, 3})
}
|
Type 关键字
type
关键字在类型断言和类型选择(type switch)的上下文中具有特殊意义,当你看到这样的语法:
1
2
|
switch value := v.(type) {
}
|
这其实是一个类型选择(type switch)的结构。v.(type)
这种语法只能在switch
语句内部使用,用于判断接口值v
的实际类型。这种结构允许你对一个接口类型的值进行多个类型断言,分别处理不同的情况。
空值(nil)
空接口与Nil比较会判定为true吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func IsNil(i interface{}) {
if i == nil {
fmt.Println("i is nil")
return
}
fmt.Println("i isn't nil")
}
func main() {
var sl []string
if sl == nil {
fmt.Println("sl is nil")
}
IsNil(sl)
}
|
看输出结果
出乎意料,为什么会导致出现这样的结果呢?
想要理解这个问题,首先需要理解 interface{} 变量的本质。
Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的空接口 interface{}。
Go 语言使用runtime.iface
表示带方法的接口,使用runtime.eface
表示不带任何方法的空接口interface{}
。
一个 interface{} 类型的变量包含了 2 个指针,一个指针指向值的类型,另外一个指针指向实际的值。在 Go 源码中 runtime 包下,我们可以找到runtime.eface
的定义。
1
2
3
4
|
type eface struct { // 16 字节
_type *_type //指向值的类型
data unsafe.Pointer //指向实际的值
}
|
从空接口的定义可以看到,当一个空接口变量为 nil 时,需要其两个指针均为 0 才行。
回到最初的问题,我们打印下传入函数中的空接口变量值,来看看它两个指针值的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 自定义结构体 模拟eface
type InterfaceStruct struct {
pt uintptr
data uintptr
}
// 定义一个转换成InterfaceStruct方法
func ToInterfaceStruct(v interface{}) InterfaceStruct {
// 将v的指针转换为InterfaceStruct类型的指针,并且对其进行了解引用(访问指针指向的变量的值)。
return *(*InterfaceStruct)(unsafe.Pointer(&v))
}
func TestRun3(t *testing.T) {
var sl []string
is := ToInterfaceStruct(sl)
inil := ToInterfaceStruct(nil)
fmt.Printf("is value is %+v\n", is)
fmt.Printf("is value is %+v\n", inil)
}
|
输出结果
1
2
|
is value is {pt:16363872 data:824634162800}
is value is {pt:0 data:0}
|