【go】静态类型与动态类型

静态类型与动态类型

1. 静态类型

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

go 复制代码
var age int   // int 是静态类型
var name string  // string 也是静态类型

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

2. 动态类型

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

这是什么意思呢?

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

比如下面这几行代码

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

比如下面这条语句

go 复制代码
var age int = 25

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

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

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

go 复制代码
package main

import "fmt"

func main() {
    age := (int)(25)
    //或者使用 age := (interface{})(25),age的静态类型是interface{}

    fmt.Printf("type: %T, data: %v ", age, age)
}

解释age := (interface{})(25)这段代码:

  1. (interface{}) 是类型转换
    • (interface{})(25) 将整型值 25 显式转换为 interface{} 类型。
    • 这种转换不会改变值本身(即动态值仍然是 25),但会将值包装成一个接口类型的变量。
  2. 静态类型
    • 静态类型是由编译器在编译时确定的类型。
    • 在这里,age 被显式声明为 interface{} 类型,因此它的静态类型是 interface{}
  3. 动态类型和动态值
    • 动态类型是实际存储的值的类型,在这里是 int
    • 动态值是实际存储的值,在这里是 25

输出如下

go 复制代码
type: int, data: 25

4. 接口细分

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

iface

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

比如

go 复制代码
type Phone interface {
   call()
}

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

​ iface 结构

iface 的源码如下:

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

比如

go 复制代码
var i interface{}

eface 的源码如下:

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

​ eface 结构组成

5.理解动态类型

在给一个空接口类型的变量赋值时,接口的内部结构会发生怎样的变化 。

iface

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

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

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

eface

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

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。eface的_type指针指的是动态类型,数据指针指的是值,但是仍有静态属性(定义时的interface{}),只是不关注

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

6. 反射的必要性

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

相关推荐
Asthenia04122 分钟前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom3 分钟前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04123 小时前
Spring 启动流程:比喻表达
后端