开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
本文已参与「开源摘星计划」,欢迎正在阅读的你加入。活动链接:github.com/weopenproje…
上一章,介绍了 go 语言中的“接口”(Interface),使用它可以实现类似于其他语言中的“抽象类”的功能。这一章,咱们继续探索,看看它还有哪些特性。
“接口“的值(Interface values)包含了接口初始化时的值,以及它对应的类型。这很好理解,先来看一段代码:
package main
import (
"fmt"
"math"
)
type I interface { // 定义接口 I,它包含函数签名 M
M()
}
type T struct { // 定义结构体 T,以及实现它的 M 方法
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64 // 定义浮点类型 F,以及实现它的 M 方法
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"} // 初始化结构体 T,注意,上面定义的时候使用了“指针接收者”,就是带了星号(*),所以这里需要获取结构体的指针
describe(i) // 打印接口 i 的值和类型
i.M()
i = F(math.Pi) // 初始化浮点类型 F,注意,上面定义的时候使用的是“数值接收者”,所以这里就不用获取指针了。下面的逻辑跟上面相同~
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
运行代码可以看到,“接口”的值就是初始化传入的值,而类型就是我们自定义实现了 M 方法的类型。
在 go 语言中,可以赋值为 nil 的接口:
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil { // 处理当值为 nil 时的情况
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T // 定义一个值为空的结构体
i = t // 赋值给 i
describe(i) // 代码正确运行,打印出了接口的值和类型
i.M()
i = &T{"hello"}
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
但是,一个 nil 接口就是啥都没有(值和类型都无):
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I // 一个 nil 接口
describe(i) // 报错,它不包含任何值和类型
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
然而,在 go 语言中,还有一种特殊的接口——空接口(empty interface),它的值和类型都为nil。这种接口有啥用呢?哈哈,一般这种都是狠角色,go 语言中的空接口可以赋值任何类型的数据:
package main
import "fmt"
func main() {
var i interface{} // 定义一个空接口
describe(i)
i = 42 // 赋值 int 类型的 42
describe(i)
i = "hello" // 赋值 string 类型的 hello
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
在需要处理多种类型数据的场景下,空接口会非常有用。比如 fmt.Print 函数,它可以接受多种类型的数据~
ok,这一章介绍了接口的值,以及两个有趣的“空”接口:nil interface 和 empty interface。下一章,继续探索接口~