目录
概述
接口是一组方法的集合, 接口中只规范方法的书写, 不包括具体的实现, 接口是动态的数据类型
我们可以把接口理解为一种行为, 比如现在有一个看黑丝的接口, 看黑丝接口中规范了一个看黑丝的方法, 如果我们又定义了一个看黑丝的方法(recever是男人), 那么男人就实现了看黑丝的接口
图示
代码演示 写一个简单的接口
- 定义一个接口
- 接口中是方法, 所以我们要定义方法
- 方法需要接收者, 所以定义一个接收者
package main
import (
"fmt"
)
// 定义一个 ColorWolf(色狼) 接口
type ColorWolf interface {
lookheisi()
}
// 由于lookheisi这个方法的接收者是 Man类型的人, 所以Man类型就实现了 ColorWolf(色狼) 接口
func (man Man) lookheisi() {
fmt.Printf("%v喜欢看黑丝", man.name)
}
// Man类型
type Man struct {
name string
age int
}
func main() {
// 此时张三出来了
zs := Man{"张三", 18}
// 张三带着黑丝走来了
zs.lookheisi()
}
实现了接口中的所有方法, 就实现了这个接口
接口类型变量
实现了对应接口的类型, 其类型的变量就可以赋值为接口类型的变量
package main
import (
"fmt"
)
type ColorWolf interface {
lookheisi()
}
func (man Man) lookheisi() {
fmt.Printf("%v喜欢看黑丝\n", man.name)
}
type Man struct {
name string
age int
}
func main() {
var man ColorWolf = Man{"张三", 18}
fmt.Printf("%v\n", man)
man.lookheisi()
}
未实现对应接口的变量, 赋值则会报错
func main() {
var man ColorWolf = "张三"
fmt.Printf("%v\n", man)
man.lookheisi()
}
大概意思就是字符串类型的张三不能赋值为ColorWolf类型, 因为字符串没有实现ColorWolf接口
接口类型赋值注意点
普通方法的接收者(包括值方法, 指针方法), 如果接收者是值类型, 它是可以同时调用值方法和指针方法的. 接口类型的方法和上面相似但有一个不同点, 如果接口方法是指针方法, 把指针方法的接收者的值赋值给接口类型是会出错的
package main
import (
"fmt"
)
type ColorWolf interface {
lookheisi()
}
func (man *Man) lookheisi() {
fmt.Printf("%v喜欢看黑丝\n", man.name)
}
type Man struct {
name string
age int
}
func main() {
var man ColorWolf
// Man类型的指针 实现了lookheisi 方法
zs := Man{"张三", 18}
zs.lookheisi()
// 但是Man类型并没有实现 lookheisi 方法
// man = zs 所以编译到这一行会报错
man = &zs // 将Man类型的指针赋值给 ColorWolf 方法是可以的
man.lookheisi()
}
空接口
接口的零值是nil 也就是所谓的空接口
空接口中没有定义任何方法, 那换个角度想如果空接口中没有方法那么就意味着所有的类型都实现了空接口, 那么继而空接口就可以保存所有类型的值
var data interface{}
fmt.Println(data == nil)
data = 1
fmt.Println(data)
data = "a"
fmt.Println(data)
data = true
fmt.Println(data)
data = [2]int{1,2}
fmt.Println(data)
data = []int{1,2,3}
fmt.Println(data)
类型断言
类型断言可以判断接口是否具有某个类型的底层数据
t, ok := i.(T) (i是接口, T是类型, t是类型为T的值, ok是有没有类型为T的值)
在遇到类似结构体这样的数据时, 通过接口直接修改数据中的某个字段时就会有问题, 此时我们就可以通过拿到底层数据来进行修改
package main
import (
"fmt"
)
type Person struct {
name string
}
func main() {
var data interface{} = Person{"张三"}
// data.name = "李四" 不能这么写 name 不是一个方法 data.name undefined (type interface {} is interface with no methods
val, ok := data.(Person)
if ok {
fmt.Printf("data的底层类型为%T\n", data)
val.name = "李四" // 由于val是底层的结构体实例 所以可以这么写
fmt.Printf("data的底层类型为%T的值是%v\n", data, val)
} else {
fmt.Println("data没有底层类型为string的值")
}
}
类型选择
类型选择一般是接口配合switch使用的, 因为接口的特殊用法只在switch中才能使用
以往的 i.(string) 可以判断接口底层是否有string类型的值
配合 switch 的 i.(type) 皆可以判断接口底层的值是什么类型的
这是一个官网的例子, 改动了一下大家更好理解点
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("i的类型和int匹配上了, i的底层数据类型为int的值是%v\n", v)
case string:
fmt.Printf("i的类型和string匹配上了, i的底层数据类型为string的值是%v\n", v)
default:
fmt.Printf("i什么都没匹配上")
}
}
func main() {
do(21)
do("hello")
do(true)
}
总结
- 接口是动态数据类型, 可以定义一组方法(行为)
- 任何实现了接口中所有方法的类型, 视为实现了接口
- 实现接口的类型可以赋值给接口类型的变量
- 接口内指针类型的方法接收者可以是指针也可以是值, 但是如果要赋值给接口变量那么必须是指针, 因为值没有实现指针方法
- 接口的零值是nil, 也就是空接口
- 任何类型都实现了空接口
- 类型断言 val, ok := i.(type) 可以查看i接口的底层数据是否有type类型的值, val为type类型的值, ok为布尔值表示是否含有type类型的值, 如果type是结构体, 那么val就会显得特别有用
- 类型选择判断接口的底层数据类型配合switch一起使用