21.接口类型
接口类型是其他类型行为的概括与抽象。接口是一种抽象类型,它并没有暴露了基于这个精确布局的内部操作。
1: type Humaner interface {2: SayHi()3: }
接口命令习惯以er结尾
接口只有方法声明,没有实现,没有数据字段
接口可以匿名嵌入其他接口,或嵌入到结构中
接口实现
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。
如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的存入接口类型的值。
代码示例:
1: package main2:3: import (
4: "fmt"
5: )6:7: type Humaner interface{8: sayHi()9: }10: type Student struct {11: name string12: id int13: }14:15: //这样就可以说Student实现了此方法
16: func (a *Student) sayHi() {17: fmt.Printf("Student(%d, %s) sayhi ", a.id, a.name)18: }19: type Teacher struct {20: addr string
21: group string22: }23: // 同上
24: func (a *Teacher) sayHi() {25: fmt.Printf("Teacher(%s, %s) sayhi ", a.addr, a.group)26: }27:28: type Mystr string
29:30: func (a *Mystr) sayHi() {31: fmt.Printf("Mystr(%s) sayhi ", *a)
32: }33:34: func main() {35: //定义接口类型的变量
36: var i Humaner37: // 只要实现了此接口方法的类型,那么这个类型的变量就可以给i赋值
38: s := &Student{"mike", 666}
39: i = s40: i.sayHi()41:42: t := &Teacher{"bj", "go"}43: i = t44: i.sayHi()45:46: var str Mystr = "hello mike"
47: i = &str48: i.sayHi()49: }
1: >>> Student(666, mike) sayhi2: Teacher(bj, go) sayhi3: Mystr(hello mike) sayhi
换一种写法,接上面,更换main函数,结果一样
1: // 定义一个普通函数,函数的参数为接口类型
2: func WhoSayHi(i Human) {3: i.sayHi()4: }5:6: func main() {7: s := &Student{"mike", 666}
8: t := &Teacher{"bj", "go"}9: var str Mystr = "hello mike"
10: // 调用同一个函数,可以有不同表现,多态性的表现
11: WhoSayHi(s)12: WhoSayHi(t)13: WhoSayHi(&str)14: }
也可以用切片
1: func main() {2: s := &Student{"mike", 666}
3: t := &Teacher{"bj", "go"}4: var str Mystr = "hello mike"
5:6: x := make([]Human, 3)7: x[0] = s8: x[1] = t9: x[2] = &str10:11: for _, i := range x{12: i.sayHi()13: }14: }
接口的继承
1: package main2:3: import "fmt"4:5: type Human interface { // 子集
6: sayHi()7: }8:9: type Person interface { //超集
10: Human // 匿名字段
11: sing(lrc string)
12: }13:14: type Student struct{15: name string16: id int17: }18:19: func (a *Student) sayHi() {20: fmt.Printf("Student[%s, %d] sayhi ", a.name, a.id)21: }22:23: func (a *Student) sing(lrc string) {
24: fmt.Println("Student在唱着:", lrc)
25: }26:27: func main() {28: //定义一个接口类型的变量
29: var i Person30: s := &Student{"Mike", 666}
31: i = s32: i.sayHi() //继承来的方法
33: i.sing("学生歌")
34: }
1: >>> Student[Mike, 666] sayhi2: Student在唱着: 学生歌
接口的转换
如上,超集可以转换为子集,反过来不可以
1: func main() {2: var iPro Person // 超集
3: iPro = &Student{"Mike", 666}
4: var i Human // 子集
5: // iPro = i 错误
6: i = iPro7: i.sayHi()8: }
1: >>> Student[Mike, 666] sayhi
空接口
空接口不包含任何的方法。因此空接口可以存储任意类型的数值,因此空接口是可以存储任意类型的数值。
1: var v1 interface{} = 12: var v2 interface{} = "abc"
3: var v3 interface{} = &v24: var v4 interface{} = struct{x int }{1}5: var v5 interface{} = &struct{x int}{1}
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
1: func Printf(fmt string,args ...interface{})
2: func Println(args ...interface{})
类型查询
我们知道interface的变量里面可以存储任意类型的数值。反向知道这个变量里面实际保存了的是那个类型的对象,两种方法
comma-ok断言
1: type Student struct {2: name string3: id int4: }5:6: func main() {7: i := make([]interface{}, 3)8: i[0] = 19: i[1] = "hello go"
10: i[2] = Student{"mike", 666}
11:12: // 类型查询,类型断言
13: for index, data := range i {14: if value, ok := data.(int); ok == true {15: fmt.Printf("x[%d] 类型为int,内容为%d ", index, value)16: } else if value, ok := data.(string); ok == true {17: fmt.Printf("x[%d] 类型为string,内容为%s ", index, value)18: } else if value, ok := data.(Student); ok == true {19: fmt.Printf("x[%d] 类型为Student,内容为%s,%d ", index, value.name, value.id)20: }21: }22: }
switch测试
1: func main() {2: i := make([]interface{}, 3)3: i[0] = 14: i[1] = "hello go"
5: i[2] = Student{"mike", 666}
6:7: for index,data := range i {8: switch value := data.(type){
9: case int:10: fmt.Printf("x[%d] 类型为int,内容为%d ", index, value)11: case string:
12: fmt.Printf("x[%d] 类型为string,内容为%s ", index, value)13: case struct:14: fmt.Printf("x[%d] 类型为Student,内容为%s,%d ", index, value.name, value.id)15: }16: }17: switch data.(type)
18: }