什么是接口(interface)
接口(interface)首先来说它是一种数据类型,里面存的数据是一组方法的集合,它只关心所包含的方法,不关心属性,也就是说属性是被它所忽略掉的。只要一个对象实现了接口定义的所有方法,那么这个对象就实现了这个接口。有点绕,举个例子:
// 定义一个接口,里面有一个Print方法
type MyInterface interface{
Print()
}
// 定义一个结构体
type MyStruct struct {
}
// 实现Print方法
func (me MyStruct) Print() {
doSomething()
}
// 定义一个参数为MyInterface接口的函数
func TestFunc(x MyInterface) {
fmt.Println(x)
}
func main() {
var me MyStruct
// 因为MyStruct实现了MyInterface定义的所有方法,它就实现了MyInterface接口
TestFunc(me) //value
var y MyInterface // 定义MyInterface接口
var m MyStruct
y = m // 因为MyStruct实现了MyInterface定义的所有方法,所以可以转换
}
从上面的例子可以看出,接口重点是方法,只要实现了定义的方法就可以调用,换一种数据类型仍然可以。
// 定义一个接口
type MyInterface interface{
Print()
}
// 定义一个新的数据类型
type myStr string
// 实现Print方法
func (s myStr) Print() {
doSomething2()
}
func TestFunc(x MyInterface) {
fmt.Println(x)
}
func main() {
var s myStr
// 它也可以被传入当做参数
TestFunc(s)
}
现在应该有所理解interface的重点了,interface其实就是一种抽象类型,它是针对具体的类型,如int、map、slice或者自己定义的类型。具体类型是有具体的值,具体的类型,具体的方法的实现。但接口只是定义包含的方法,这些方法并不是由接口直接实现的,而是通过用户定义的类型来实现该方法,比如上面的例子中的MyStruct,myStr。
存在既有道理,接口的优势就是通用,
这个像是动态语言里的“鸭子类型”,一个对象只要”看起来像鸭子,走起来像鸭子“,那么它就可以被看成是鸭子。
// 定义一个鸭子接口
type Duck interface{
Walk()
}
// 定义一个猪结构体
type Pig struct {
}
// 实现Walk方法
func (p Pig) Walk() {
fmt.Println("pig walk")
}
// 定义一个狗结构体
type Dog struct {
}
// 实现Walk方法
func (d Dog) Walk() {
fmt.Println("dog walk")
}
// 再定义一个必须传入鸭子作为参数的函数
func DuckWalk(x Duck) {
x.Walk()
}
func main() {
var p Pig
var d Dog
// 猪和够都可以被传入当做参数
DuckWalk(p) //正常
DuckWalk(d) //正常
}
这就是鸭子类型的优点,只要实现了定义的方法就能够调用,并不要考虑具体的方法实现是否一样。
一个函数的传入参数如果被规定为一种具体类型,那么你就只能传入该类型的数据作为参数。再定义一个函数PigWalk,参数是Pig结构体类型:
func PigWalk(p Pig) {
p.Walk()
}
如果你传入类型不是Pig, 就会报错:
func main() {
var d Dog
PigWalk(d)
}
# command-line-arguments
.\exe_time.go:37:9: cannot use d (type Dog) as type Pig in argument to PigWalk
判断interface存储的变量是哪种类型
一个对象转换成一个接口时,会失去它原来的类型,go提供了一种判断方式,断言:value, ok := em.(T)
:em 是 interface 类型的变量,T代表要断言的类型,value 是 interface 变量存储的值,ok 是 bool 类型表示此次言断是否成功。
var i interface{}
var s string
// 将s转换为一个空接口类型
i = s
if v, ok := i.(string); ok {
fmt.Println(i, "is string type")
} else {
fmt.Println(i, "isn't string type")
}
ok是true表示i存储的是string类型,false则不是,这就是类型言断(Type assertions)
如果需要区分多种类型,可以使用switch断言,能够一次性区分多种类型,但是只能在switch中使用:
switch t := i.(type) {
case string:
fmt.Println("i store string", t)
case int:
fmt.Println("i store int", t)
}
空接口
不带任何方法的interface就是一个空接口:empty interface
type empty interface{}
因为空接口不带任何方法,那就是它没有要求 ,既然没有要求那么所有的方法都可以啦,因此所有的类型实现了空接口。
举例:我们常用的fmt.println()
函数参数就是空接口,任何类型都可以传入:
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
既然empty interface可以接受任何类型的参数,空接口的slice([]interface{}
)是否也可以接受任何类型的slice呢?试一下吧
func printSlice(ins []interface{}) {
for _, in := range ins {
fmt.Println(in)
}
}
func main(){
names := []string{"chen", "wo", "chong"}
printSlice(names)
}
结果:
# command-line-arguments
.\test_interface_slice.go:13:12: cannot use names (type []string) as type []interface {} in argument to printSlice
报错了,行不通,说明没有帮助我们自动把 slice 转换成 interface{}
类型的 slice,所以出错了。go 不会对 类型是interface{} 的 slice 进行转换 。为什么 go 不帮我们自动转换,一开始我也很好奇,最后终于在 go 的 wiki 中找到了答案 https://github.com/golang/go/wiki/InterfaceSlice 大意是 interface{}
会占用两个字长的存储空间,一个是自身的 methods 数据,一个是指向其存储值的指针,也就是 interface 变量存储的值,因而 slice []interface{} 其长度是固定的N*2
,但是 []T 的长度是N*sizeof(T)
,两种 slice 实际存储值的大小是有区别的(文中只介绍两种 slice 的不同,至于为什么不能转换猜测可能是 runtime 转换代价比较大)。
但是我们可以手动进行转换来达到我们的目的。
var dataSlice []int = []int{1,2,3,4,5,6}
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) // 创建该长度的[]interface{}
// 手动遍历
for i, d := range dataSlice {
interfaceSlice[i] = d
}
接口的接收者
什么是接收者,在实现一个接口时,一个对象必须要实现接口提供的所有方法,而实现了所有方法的对象就可以称为方法的接收者( receiver )。
func main() {
var a animal
var p pig
a=p
a.Show()
//使用另外一个类型赋值
var d dog
a=d
a.Show()
}
type animal interface {
Show()
}
type pig int
type dog int
func (p pig) Show(){
fmt.Println("papapa")
}
func (d dog) Show(){
fmt.Println("wanwan")
}
实现方法的时候,可以通过对象的指针实现,也可以通过对象的值来实现,所以方法的接受者有两种,指针接收者(pointer receiver),值接收者(value receiver)。
type Sheep int
// 值接收者
func (s Sheep) Show(){
fmt.Println("mi-1")
}
// 指针接收者
func (s *Sheep) Show(){
fmt.Println("mi-2")
}
它们两个还是有区别的,
如果对象通过value实现了该方法, 那就是这个对象的值实现了这个接口;
如果是对象通过pointer实现了该方法,那就是这个对象的指针实现了这个接口;
receiver是pointer
type animal interface {
Show()
}
type bird int
// 指针接收者
func (b *bird) Show() {
fmt.Println("bird")
}
func ShowFunc(a animal) {
a.Show()
}
用对象的pointer调用, 结果正常
func main() {
var b bird
ShowFunc(&b)
}
但用对象的value调用,就会报错
ShowFunc(b)
# command-line-arguments
.\haha.go:7:10: cannot use b (type bird) as type animal in argument to ShowFunc:
bird does not implement animal (Show method has pointer receiver)
receiver是value
type animal interface {
Show()
}
type cat int
// 指针接收者
func (c cat) Show() {
fmt.Println("cat")
}
func ShowFunc(a animal) {
a.Show()
}
func main() {
var c cat
ShowFunc(c) // value
ShowFunc(&c) // pointer
}
值接收者( value receiver)实现接口,无论是 pointer 还是 value 都可以正确执行。
为什么呢?
在go执行过程中,如果是按 pointer 调用,go 会自动进行转换,因为有了pointer 总是能得到指针指向的value 是什么;如果是 value 调用,go 将无从得知 value 的原始值是什么,因为 value 是份拷贝,它在内存里的地址已经变化了。go 会把指针进行隐式转换得到 value,但反过来则不行。
通过这个例子我们可以得出结论:
Methods Receivers | Values |
---|---|
(t T) | T and *T |
(t *T) | *T |
实体类型以值接收者实现接口的时候,不管是实体类型的值,还是实体类型值的指针,都实现了该接口。
实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。
其次我们我们以实体类型是值还是指针的角度看。
Values | Methods Receivers |
---|---|
T | (t T) |
*T | (t T) and (t *T) |
上面的表格可以解读为:类型的值只能实现值接收者的接口;指向类型的指针,既可以实现值接收者的接口,也可以实现指针接收者的接口。
如果觉得对您有所帮助的话,点个赞呗~,让我能够有写下去的动力。
有疑问加站长微信联系(非本文作者)