go interface 转 string_2020重学Go系列:34. 图解静态类型与动态类型

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 对组合而成,pair 对中记录着实际变量的值和类型。

比如下面这条语句

var age int = 25

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

b35f72f03bbe0c60ad67483d2b575de5.png

知道了接口的组成后,我们在定义一个变量时,除了使用常规的方法(可参考: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 的具体结构可用如下一张图来表示

c52102fb56e2bf75f201dd30dded3dc5.png
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
}
64a13b1ed37b8cc837ea815ec706a9ca.png
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,暂无动态类型。

4efba34491ff27d706fb86c6461cbda5.png

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

1fcfb6ebbf3d73c2086712e7eccd5b62.png

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。

39f9150bada74c27ce044a84388e8814.png

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

7046c0aaec54981382551079c6e5f255.png

6. 反射的必要性

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

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

推荐阅读

  • 2020重学Go系列:33. 如何手动实现一个协程池?


喜欢本文的朋友,欢迎关注“Go语言中文网”:

0c7ecb1d31a7a5e8211faa909b19bba6.png

Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值