接口
- 接口是一组包含方法名、参数、返回值的为具体实现的方法的集合。
- 它定义的是方法的定义。
举个例子:
package main import "fmt" type Cock struct { Name string } func (c Cock) Sing() { fmt.Println(c.Name, "这只鸡在唱歌") } func Sing(c Cock) { c.Sing() } func main() { c := Cock{Name: "小坤坤"} Sing(c) }
如上述代码,如果有别的结构体有同样的方法想去调用这个接口,那么它会报错,调用不了。
package main import "fmt" // 接口 type Cock struct { Name string } func (c Cock) Sing() { fmt.Println(c.Name, "这只鸡在唱歌") } func Sing(c Cock) { c.Sing() } type Cat struct { Name string } func (c Cat) Sing() { fmt.Println(c.Name, "这只猫在唱歌") } func main() { co := Cock{Name: "小坤坤"} ca := Cat{Name: "缪缪"} Sing(co) Sing(ca) //报错,因为Sing这个接口的参数只接受Cock //如果我们想用的话,不是ca.Sing(),就是重新定义一个属于Cat的Sing方法。 //但这样明显操作很繁琐 }
所以我们可以定义一个接口:
package main import "fmt" type Cock struct { Name string } // 定义一个接口 type SingInterface interface { Sing() } func (c Cock) Sing() { fmt.Println(c.Name, "这只鸡在唱歌") } // 有了接口之后,就不指定类型了,直接使用接口。 // func Sing(c Cock) { func Sing(c SingInterface) { c.Sing() } type Cat struct { Name string } func (c Cat) Sing() { fmt.Println(c.Name, "这只猫在唱歌") } func main() { co := Cock{Name: "小坤坤"} ca := Cat{Name: "缪缪"} Sing(co) Sing(ca) //当有了接口之后,就不会报错了 }
如果要在接口里面用属性的话,要先自己实现一个获取属性的方法:
package main import "fmt" type Cock struct { Name string } // 定义一个接口 type SingInterface interface { Sing() GetName() string } func (c Cock) Sing() { fmt.Println(c.Name, "这只鸡在唱歌") } func (c Cock) GetName() string { return c.Name } // 有了接口之后,就不指定类型了,直接使用接口。 // func Sing(c Cock) { func Sing(c SingInterface) { //其实这个时候的名字叫FuncInterface()更好,这个不只是Sing了。 c.Sing() fmt.Println(c.GetName()) } type Cat struct { Name string } func (c Cat) Sing() { fmt.Println(c.Name, "这只猫在唱歌") } func (c Cat) GetName() string { return c.Name } func main() { co := Cock{Name: "小坤坤"} ca := Cat{Name: "缪缪"} Sing(co) Sing(ca) //当有了接口之后,就不会报错了 } //这样两个相似对象都能用一个接口实现。 //在相似对象多的时候使用接口,能使代码简洁很多。
- 接口本身是不能绑定方法的,它默认是值传递。
通过接口,我们还可以知道什么类型的值调用了接口。进行类型断言即可。
func Sing(c SingInterface) { ch := c.(Cock) //断言是Cock类型调用了接口 fmt.Println(ch) c.Sing() fmt.Println(c.GetName()) } //其他代码和上面例子相同 /* 输出:{小坤坤} 小坤坤 这只鸡在唱歌 小坤坤 panic: interface conversion: main.SingInterface is main.Cat, not main.Cock */ //上面报错可以看出Cat类型被断言失败,是Cat类型不是Cock类型。 func Sing(c SingInterface) { ch, ok := c.(Cock) //用ok查看是否断言成功 fmt.Println(ch, ok) c.Sing() fmt.Println(c.GetName()) } //输出: /* {小坤坤} true 小坤坤 这只鸡在唱歌 小坤坤 {} false 缪缪 这只猫在唱歌 缪缪 */ //虽然输出不一样了,但还是可以通过bool值ok看出类型断言失败了。
这还只是断言一种类型,我们可以断言所有类型:
func Sing(c SingInterface) { switch ctype := c.(type) { case Cock: fmt.Println(ctype) case Cat: fmt.Println(ctype) default: fmt.Println("其他") } c.Sing() fmt.Println(c.GetName()) } //输出: /* {小坤坤} 小坤坤 这只鸡在唱歌 小坤坤 {缪缪} 缪缪 这只猫在唱歌 缪缪 */ //类型都被断言成功了。
- 空接口:没有定义任何方法的接口为空接口。空接口可以接受任何类型。也就是说任何类型都能实现空接口的定义。
- 空接口可直接使用作为类型声明的一个实例,这个实例就能承载任何类型的值。
- 空接口可以承载任何类型的数组或者切片。但是当空接口承载了数组或者切片以后,这个对象无法再进行切片。
- 空接口的类型对象不能赋值给另一个固定类型的对象。
空接口用的很多,也很重要。例如常用的
print
就用到了空接口。tyep EmptyInterface interface { } func Print(val EmptyInterface){ fmt.Println(val) }
在简洁点的写法:
func Print(val interface{}){ fmt.Println(val) }
甚至再简洁一点:
func Print(val any){ fmt.Println(val) } //其实any就是interface{}的别名
评论(0)