7.1. 空接口-万能封包器

在凹语言中,最简单的接口是空接口,既 interface {},声明接口类型变量的方法跟其它类型一致,例如下面的代码声明了一个名为 i 的空接口变量:

    i: interface{}

习惯上我们一般将 接口类型变量 称为 接口值。空接口有一个非常独特的特性:任何类型的值都可以赋值给空接口值,例如下面的操做全是合法的:

    iface: interface{}

    iface = 777         // 无类型整数赋值给空接口
    iface = 13.14       // 无类型浮点数赋值给空接口
    iface = "你好,空接口" // 字符串赋值给空接口

    i: i64 = 58372665865
    iface = i // 64位整数赋值给空接口

    // 匿名结构体赋值给空接口:
    iface = struct{name: string; age: i32}{name: "凹语言", age: 1}

这种赋值行为执行的是传值操作,相当于在接口值内复制了一份原始数据的拷贝,这份拷贝被称为接口值的具体值,具体值的类型被称为具体类型

那么如何判断一个已被赋值的接口值所持有的具体类型?如何读取具体值?这就需要用到类型断言语法,它的一般形式为:

    v, ok = iface.(Type) // 断言iface的具体类型是否为Type

其中 v 是类型为 Type 的值, okbool 型值,该语句执行后,若 oktrue,则表明接口值 iface 的具体类型确实是 Type,并且其具体值将被赋予 v;否则表明 iface 的具体类型不为 Type。实际示例如下:

// 版权 @2021 凹语言 作者。保留所有权利。

type T1 struct {
    a: i32
}

func main {
    ival: i32 = 777
    printConcrete(ival)       // i32: 777
    printConcrete("你好凹语言") // string: 你好凹语言

    v1 := T1{a: 42}
    printConcrete(v1) // T1, T1.a: 42

    printConcrete(13.14) // 未知类型
}

func printConcrete(iface: interface{}) {
    ok: bool
    i: i32
    s: string
    t: T1

    i, ok = iface.(i32)
    if ok {
        println("i32:", i)
        return
    }

    s, ok = iface.(string)
    if ok {
        println("string:", s)
        return
    }

    t, ok = iface.(T1)
    if ok {
        println("T1, T1.a:", t.a)
        return
    }

    println("未知类型")
}

在函数 printConcrete 内,通过接口类型断言,可以动态的判断传入的空接口值的具体类型,并获取其具体值。由于函数内未进行浮点数断言,因此输入浮点数时会输出“未知类型”。

注意函数 printConcrete 的参数类型为空接口(interface{}),在 main 函数中调用它时,实际上执行了隐式转换(拷贝),比如语句 printConcrete(ival) 实际上等价于:

    iface: interface{} = ival
    printConcrete(iface)

凹语言在绝大多数情况下不允许隐式类型转换,但接口是个例外。当函数参数类型为接口时,若调用方填入的实参是具体类型,则编译器会自动执行赋值转换的操作。

如果接口值的具体类型存在多种可能,那么使用多个类型断言加条件判断的方法无疑很累赘,在这种场景下,可以使用switch...case...格式的分支类型断言,例如上述 printConcrete 函数可以改写为:

func printConcrete(iface: interface{}) {
    //分支类型断言
    switch v := iface.(type) {
    case i32:
        println("i32:", v)  // v是iface的具体值,该分支下,其类型为 i32,下同

    case string:
        println("string:", v)

    case T1:
        println("T1, T1.a:", v.a)

    default:
        println("未知类型")
    }
}

其中 iface.(type) 是固定写法,后续每个 case 分支表示具体类型满足该分支条件。

任何类型的值都可以赋予空接口,它在凹语言中实际起到了万能封包器的作用,经常用于在函数间传递类型会动态变化的值。

本文中“空接口”指 interface{},既方法集为空的接口类型,下一节中的“非空接口”指方法集不为空的接口类型;当我们要描述值为0的接口值时,将使用“0值接口”,或“nil接口”,请注意区分。