静态类型与动态类型
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)这段代码:
(interface{})
是类型转换(interface{})(25)
将整型值25
显式转换为interface{}
类型。- 这种转换不会改变值本身(即动态值仍然是
25
),但会将值包装成一个接口类型的变量。
- 静态类型
- 静态类型是由编译器在编译时确定的类型。
- 在这里,
age
被显式声明为interface{}
类型,因此它的静态类型是interface{}
。
- 动态类型和动态值
- 动态类型是实际存储的值的类型,在这里是
int
。 - 动态值是实际存储的值,在这里是
25
。
- 动态类型是实际存储的值的类型,在这里是
输出如下
go
type: int, data: 25
4. 接口细分
根据接口是否包含方法,可以将接口分为 iface
和 eface
。
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. 反射的必要性
由于动态类型的存在,在一个函数中接收的参数的类型有可能无法预先知晓,此时我们就要对参数进行反射,然后根据不同的类型做不同的处理。