2.9 详细图解:静态类型与动态类型 ================================ .. image:: http://image.iswbm.com/20200607145423.png 1. 静态类型 ----------- 所谓的\ **静态类型**\ (即 static type),就是变量声明的时候的类型。 .. code:: go var age int // int 是静态类型 var name string // string 也是静态类型 它是你在编码时,肉眼可见的类型。 2. 动态类型 ----------- 所谓的 **动态类型**\ (即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。 这是什么意思呢? 我们都知道 **空接口** 可以承接任意类型的值,什么 int 呀,string 呀,都可以接收。 比如下面这几行代码 .. code:: go var i interface{} i = 18 i = "Go编程时光" 第一行:我们在给 ``i`` 声明了 ``interface{}`` 类型,所以 ``i`` 的静态类型就是 ``interface{}`` 第二行:当我们给变量 ``i`` 赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。 第三行:当我们给变量 ``i`` 赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。 从以上,可以知道,不管是 ``i=18`` ,还是 ``i="Go编程时光"``\ ,都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的 动态类型是程序运行时系统才能看见的类型。 3. 接口组成 ----------- 每个接口变量,实际上都是由一 pair 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。 比如下面这条语句 .. code:: go var age int = 25 我们声明了一个 int 类型变量,变量名叫 age ,其值为 25 .. image:: http://image.iswbm.com/20200610235106.png 知道了接口的组成后,我们在定义一个变量时,除了使用常规的方法(可参考:\ `02. 学习五种变量创建的方法 `__\ ) 也可以使用像下面这样的方式 .. code:: go package main import "fmt" func main() { age := (int)(25) //或者使用 age := (interface{})(25) fmt.Printf("type: %T, data: %v ", age, age) } 输出如下 .. code:: go type: int, data: 25 4. 接口细分 ----------- 根据接口是否包含方法,可以将接口分为 ``iface`` 和 ``eface``\ 。 iface ~~~~~ 第一种:\ **iface**\ ,表示带有一组方法的接口。 比如 .. code:: go type Phone interface { call() } ``iface`` 的具体结构可用如下一张图来表示 .. figure:: http://image.iswbm.com/20200610220830.png :alt: iface 结构 iface 结构 iface 的源码如下: .. code:: go // runtime/runtime2.go // 非空接口 type iface struct { tab *itab data unsafe.Pointer } // 非空接口的类型信息 type itab struct { inter *interfacetype // 接口定义的类型信息 _type *_type // 接口实际指向值的类型信息 link *itab bad int32 inhash int32 fun [1]uintptr // 接口方法实现列表,即函数地址列表,按字典序排序 } // runtime/type.go // 非空接口类型,接口定义,包路径等。 type interfacetype struct { typ _type pkgpath name mhdr []imethod // 接口方法声明列表,按字典序排序 } // 接口的方法声明 type imethod struct { name nameOff // 方法名 ityp typeOff // 描述方法参数返回值等细节 } eface ~~~~~ 第二种:\ **eface**\ ,表示不带有方法的接口 比如 .. code:: go var i interface{} eface 的源码如下: .. code:: go // src/runtime/runtime2.go // 空接口 type eface struct { _type *_type data unsafe.Pointer } .. figure:: http://image.iswbm.com/20200610221213.png :alt: eface 结构组成 eface 结构组成 5.理解动态类型 -------------- 前两节,我们知道了什么是动态类型?如何让一个对象具有动态类型? 后两节,我们知道了接口分两种,它们的内部结构各是什么样的? 那最后一节,可以将前面四节的内容结合起来,看看在给一个空接口类型的变量赋值时,接口的内部结构会发生怎样的变化 。 .. _iface-1: iface ~~~~~ 先来看看 iface,有如下一段代码: .. code:: go var reader io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } reader = tty 第一行代码:var reader io.Reader ,由于 io.Reader 接口包含 Read 方法,所以 io.Reader 是 ``iface``\ ,此时 reader 对象的静态类型是 io.Reader,暂无动态类型。 .. image:: http://image.iswbm.com/image-20200610225323018.png 最后一行代码:reader = tty,tty 是一个 ``*os.File`` 类型的实例,此时reader 对象的静态类型还是 io.Reader,而动态类型变成了 ``*os.File``\ 。 .. image:: http://image.iswbm.com/20200610230951.png .. _eface-1: eface ~~~~~ 再来看看 eface,有如下一段代码: .. code:: go //不带函数的interface var empty interface{} tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } empty = tty 第一行代码:var empty interface{},由于 ``interface{}`` 是一个 eface,其只有一个 ``_type`` 可以存放变量类型,此时 empty 对象的(静态)类型是 nil。 .. image:: http://image.iswbm.com/image-20200610230819030.png 最后一行代码:empty = tty,tty 是一个 ``*os.File`` 类型的实例,此时 ``_type`` 变成了 ``*os.File``\ 。 .. image:: http://image.iswbm.com/image-20200610231015612.png 6. 反射的必要性 --------------- 由于动态类型的存在,在一个函数中接收的参数的类型有可能无法预先知晓,此时我们就要对参数进行反射,然后根据不同的类型做不同的处理。 关于 反射 的内容有点多,我将其安排在另外两篇文章:。 - `学习反射:反射三定律 `__ - `学习反射:全面学习反射的函数 `__ 参考文章 -------- - `图解go反射实现原理 `__