# 2.9 详细图解:静态类型与动态类型
## 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. 学习五种变量创建的方法**](http://mp.weixin.qq.com/s?__biz=MzU1NzU1MTM2NA==&mid=2247483669&idx=2&sn=e70a1400c094e981f15b8da552bd8fbf&chksm=fc355b7ecb42d26824985163a3ef0c3567134975637c4efc42161751f54ab10343b485b36e23&scene=21#wechat_redirect))
也可以使用像下面这样的方式
```go
package main
import "fmt"
func main() {
age := (int)(25)
//或者使用 age := (interface{})(25)
fmt.Printf("type: %T, data: %v ", age, age)
}
```
输出如下
```go
type: int, data: 25
```
## 4. 接口细分
根据接口是否包含方法,可以将接口分为 `iface` 和 `eface`。
### iface
第一种:**iface**,表示带有一组方法的接口。
比如
```go
type Phone interface {
call()
}
```
`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
}
```

## 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。

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

## 6. 反射的必要性
由于动态类型的存在,在一个函数中接收的参数的类型有可能无法预先知晓,此时我们就要对参数进行反射,然后根据不同的类型做不同的处理。
关于 反射 的内容有点多,我将其安排在另外两篇文章:。
- [学习反射:反射三定律](http://golang.iswbm.com/en/latest/c02/c02_07.html)
- [学习反射:全面学习反射的函数](http://golang.iswbm.com/en/latest/c02/c02_08.html)
## 参考文章
- [图解go反射实现原理](https://i6448038.github.io/2020/02/15/golang-reflection/)
- 第一章:基础知识
- 1.1 一文搞定开发环境的搭建
- 1.2 五种变量创建的方法
- 1.3 数据类型:整型与浮点型
- 1.4 数据类型:byte、rune与字符串
- 1.5 数据类型:数组与切片
- 1.6 数据类型:字典与布尔类型
- 1.7 数据类型:指针
- 1.8 流程控制:if-else
- 1.9 流程控制:switch-case
- 1.10 流程控制:for 循环
- 1.11 流程控制:goto 无条件跳转
- 1.12 流程控制:defer 延迟语句
- 1.13 流程控制:理解 select 用法
- 1.14 异常机制:panic 和 recover
- 1.15 语法规则:理解语句块与作用域
- 第二章:面向对象
- 2.1 面向对象:结构体与继承
- 2.2 面向对象:接口与多态
- 2.3 面向对象:结构体里的 Tag 用法
- 2.4 学习接口:详解类型断言
- 2.5 学习接口:Go 语言中的空接口
- 2.6 学习接口:接口的三个"潜规则"
- 2.7 学习反射:反射三定律
- 2.8 学习反射:全面学习反射的函数
- 2.9 详细图解:静态类型与动态类型
- 2.10 关键字:make 和 new 的区别?
- 第三章:项目管理
- 3.1 依赖管理:包导入很重要的 8 个知识点
- 3.2 依赖管理:超详细解读 Go Modules 应用
- 3.3 开源发布:如何开源自己写的包给别人用?
- 3.4 代码规范:Go语言中编码规范
- 第四章:并发编程
- 4.1 学习 Go 函数:理解 Go 里的函数
- 4.2 学习 Go 协程:goroutine
- 4.3 学习 Go 协程:详解信道/通道
- 4.4 学习 Go 协程:WaitGroup
- 4.5 学习 Go 协程:互斥锁和读写锁
- 4.7 学习 Go 协程: 信道死锁经典错误案例
- 4.7 学习 Go 协程:如何实现一个协程池?
- 4.8 理解 Go 语言中的 Context
- 4.9 学习一些常见的并发模型
- 第五章:学标准库
- 5.1 fmt.Printf 方法详解
- 5.2 os/exec 执行命令的五种姿势
- 第六章:开发技能
- 6.1 Go 命令:go test 工具详解
- 6.2 单元测试:如何进行单元测试?
- 6.3 调试技巧:使用 GDB 调试 Go 程序
- 6.4 Go 命令: Go 命令指南
- 第七章:暂未分类
- 7.1 20 个学习 Go 语言的精品网站
