Go基础-接口

Go语言基础系列学习文章

类型断言

在Go语言中,类型断言是一种检查并确认接口变量底层具体类型的操作。类型断言的语法格式如下:

1
value, ok := x.(T)

这里,x 表示一个接口的变量,而T 表示一个类型。类型断言返回两个值:valueok

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
var i interface{}

举个栗子🌰:

 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)
}

看输出结果

1
2
sl为nil
i isn't nil

出乎意料,为什么会导致出现这样的结果呢?

想要理解这个问题,首先需要理解 interface{} 变量的本质。

Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的空接口 interface{}。

Go 语言使用runtime.iface表示带方法的接口,使用runtime.eface表示不带任何方法的空接口interface{}

golang-interface-difference

一个 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}
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy