掘金 后端 ( ) • 2024-04-25 14:19

本文是介绍golang接口类型的第一篇,主要介绍接口类型与接口类型的值的相关概念。

1. 静态类型、动态类型、动态值

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

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

它是你在编码时,肉眼可见的类型。 所谓的 动态类型(即 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编程",都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的动态类型是程序运行时系统才能看见的类型

2. 接口类型的值

由于接口类型的值可以是任意一个实现该接口的类型值,所以除了具体的值,接口值还需要记录这个值的类型。也就是说接口值由类型(type)和值(value)组成。鉴于这两部分会根据存入的值不同而发送变化,我们称之为接口的动态类型动态值

type Mover interface {
    Move()
}
​
type Dog struct {
    Name string 
}
​
func (d *Dog) Move() {
    fmt.Println("狗在跑")
}
​
type Car struct {
    Brand string
}
​
func (c *Car) Move() {
    fmt.Println("汽车在跑")
}
​
var m Mover

image.png

fmt.Println(m==nil)  // true

此时将一个*Dog结构体赋值给m

m = &Dog{Name : "旺财"}

image.png 为接口变量m赋值一个*Car类型的值 go

m = new(Car)

image.png

fmt.Println(m==nil)  //false

此时仅仅是动态值是nil,动态类型不是nil,所以m不等于nil 接口值支持比较,当且仅当接口值的动态类型和动态值相等时相等

var (
    x Mover = new(Dog)
    y Mover = new(Car)
)

fmt.Println(x==y)   // false

Go 语言中,interface 的内部实现包含了 2 个字段,类型 T 和 值 V,interface 可以使用 == 或 != 比较。2 个 interface 相等有以下 2 种情况

  1. 两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
  2. 类型 T 相同,且对应的值 V 相等。

看下面的例子:

type Stu struct {
	Name string
}

type StuInt interface{}

func main() {
	var stu1, stu2 StuInt = &Stu{"Tom"}, &Stu{"Tom"}
	var stu3, stu4 StuInt = Stu{"Tom"}, Stu{"Tom"}
	fmt.Println(stu1 == stu2) // false
	fmt.Println(stu3 == stu4) // true
}

stu1 和 stu2 对应的类型是 *Stu,值是 Stu 结构体的地址,两个地址不同,因此结果为 false。 stu3 和 stu4 对应的类型是 Stu,值是 Stu 结构体,且各字段相等,因此结果为 true。

总结

  1. 接口类型的值包含动态类型和动态值。
  2. 接口类型的变量支持相互比较,只有当两个变量的动态类型和动态值都相等时两者才相等。
  3. 接口类型的变量支持和nil比较,只有当动态类型和动态值都为nil的时候两者才相等。