动态类型与动态值

动态类型是接口值里的具体类型信息,动态值是具体类型的值

接口值的比较

当且仅当两个接口都是nil,或者他们的动态类型相同并且动态值也根据这个动态类型的==操作符相等。

因为接口值是可以比较的,所以它可以作为map键或做为switch语句的操作数

如果两个接口值动态类型相同,但是这个动态类型是不可比较的(Slice)那么就会比较失败并panic

一个包含nil指针的接口不等于nil接口

  • 包含nil指针的接口,里面的nil指针有具体的类型,只是值是nil,所以这个接口实际不是nil,而nil接口里面什么都没有

类型断言

x.(T):判断这个变量x是不是T类型

  • 断言的是具体类型(int, string …)

    如果成功,可以调用这个具体类型的所有方法和属性
    失败会panic

    1
    2
    3
    4
    5
    var w io.Writer
    w = os.Stdout

    f := w.(*os.File)//断言成功,里面确实是*os.File类型,可以调用Close(), Name()等io.Writer接口没有的方法
    c := w.(*bytes.Buffer)//失败,不是Buffer
  • 断言的是接口类型
    T不是具体的类型,而是另一个接口,比如io.ReadWriter
    检查x里面的东西是不是实现了T这个接口
    成功,动态类型和动态值没变,能调用的方法变了,失败panic
    本来有一个Writer类型的w,里面是一个File对象,只能调用Write()方法,现在做类型断言rw := w.(io.ReadWriter),现在里面的东西能Write,我猜测它可以Read(实现ReadWriter)
    检查File对象确实有Read()方法,返回一个新变量,rw里面还是那个File对象,动态值没变,但是类型变为了io.ReadWriter

类型断言询问行为

  • web服务器需要向网络连接(w, io.Writer)写入字符串,但是io.Writer接口只定义了Write([]byte)方法,为了适配接口,要将字符串强转为[]byte,这会在内存中分配一段新的空间并进行数据拷贝,虽然用后就丢掉,但是在服务器中,频繁的分配会影响性能

虽然w的表面类型是io.Writer,但是底层的具体对象往往有强大的功能,比如net/http的网络连接,*os.File, *bytes.Buffer, 这些类型都有一个WriteString(s string)的方法,能直接把字符串写到底层, 不用转换为[]byte

不能保证所有的io.Writer都有WriteString, 需要一种试探的方法:在函数内定义一个临时接口stringWriter, 里面包含WriteString方法, w.(stringWriter)成功,说明底层对象实现了WriteString,直接调用此方法避免内存分配,如果失败,就老老实实的转[]byte再调用Write

Go标准库直接提供了io.WriterString(w, s)函数,所以不需要写上面的判断逻辑,它会自动帮你做这种优化

fmt.Fprintf也是这么做的,当fmt需要打印一个x时, 也会进行‘试探’,

  • 试探 x.(error):你有 Error() 方法吗?如果有,我就把它当错误打印。

  • 试探 x.(Stringer):你有 String() 方法吗?如果有,我就调用它获取字符串来打印。

  • 兜底:都没有?那我用反射(Reflection)去分析你的结构打印出来