• 34. 图解 Go 语言:静态类型与动态类型


    Hi,大家好,我是明哥。

    在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。

    我的在线博客:http://golang.iswbm.com
    我的 Github:github.com/iswbm/GolangCodingTime


    1. 静态类型

    所谓的静态类型(即 static type),就是变量声明的时候的类型。

    var age int   // int 是静态类型
    var name string  // string 也是静态类型
    

    它是你在编码时,肉眼可见的类型。

    2. 动态类型

    所谓的 动态类型(即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。

    这是什么意思呢?

    我们都知道 空接口 可以承接什么问题类型的值,什么 int 呀,string 呀,都可以接收。

    比如下面这几行代码

    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 对中记录着实际变量的值和类型。

    比如下面这条语句

    var age int = 25
    

    我们声明了一个 int 类型变量,变量名叫 age ,其值为 25

    知道了接口的组成后,我们在定义一个变量时,除了使用常规的方法(可参考:02. 学习五种变量创建的方法

    也可以使用像下面这样的方式

    package main
    
    import "fmt"
    
    func main() {
        age := (int)(25)
        //或者使用 age := (interface{})(25)
        
        fmt.Printf("type: %T, data: %v ", age, age)
    }
    

    输出如下

    type: int, data: 25
    

    4. 接口细分

    根据接口是否包含方法,可以将接口分为 ifaceeface

    iface

    第一种:iface,表示带有一组方法的接口。

    比如

    type Phone interface {
       call()
    }
    

    iface 的具体结构可用如下一张图来表示

    iface 结构

    iface 的源码如下:

    // 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,表示不带有方法的接口

    比如

    var i interface{} 
    

    eface 的源码如下:

    // src/runtime/runtime2.go
    // 空接口
    type eface struct {
        _type *_type
        data  unsafe.Pointer
    }
    

    eface 结构组成

    5.理解动态类型

    前两节,我们知道了什么是动态类型?如何让一个对象具有动态类型?

    后两节,我们知道了接口分两种,它们的内部结构各是什么样的?

    那最后一节,可以将前面四节的内容结合起来,看看在给一个空接口类型的变量赋值时,接口的内部结构会发生怎样的变化 。

    iface

    先来看看 iface,有如下一段代码:

    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,暂无动态类型。

    最后一行代码:reader = tty,tty 是一个 *os.File 类型的实例,此时reader 对象的静态类型还是 io.Reader,而动态类型变成了 *os.File

    eface

    再来看看 eface,有如下一段代码:

    //不带函数的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。

    最后一行代码:empty = tty,tty 是一个 *os.File 类型的实例,此时 _type 变成了 *os.File

    6. 反射的必要性

    由于动态类型的存在,在一个函数中接收的参数的类型有可能无法预先知晓,此时我们就要对参数进行反射,然后根据不同的类型做不同的处理。

    关于 反射 的内容有点多,我将其安排在下一篇。

    参考文章

  • 相关阅读:
    [LeetCode] 278. First Bad Version 第一个坏版本
    [LeetCode] 119. Pascal's Triangle II 杨辉三角 II
    [LeetCode] 118. Pascal's Triangle 杨辉三角
    [LeetCode] 272. Closest Binary Search Tree Value II 最近的二叉搜索树的值 II
    校验数组中是否存在某一个元素
    css sprites 图片位置计算
    后台获取当前客户端浏览器的类型
    Linq,拉姆达表达式注意!
    window.open 设置高和宽无效
    asp:FileUpload 控件上传多文件
  • 原文地址:https://www.cnblogs.com/wongbingming/p/13128946.html
Copyright © 2020-2023  润新知