接口是一种约定,它是一个抽象的类型,和咱们见到的具体的类型如int、map、slice等不同。具体的类型,咱们能够知道它是什么,而且能够知道能够用它作什么;可是接口不同,接口是抽象的,它只有一组接口方法,咱们并不知道它的内部实现,因此咱们不知道接口是什么,可是咱们知道能够利用它提供的方法作什么。数据结构
抽象就是接口的优点,它不用和具体的实现细节绑定在一块儿,咱们只需定义接口,告诉编码人员它能够作什么,这样咱们能够把具体实现分开,编码就会更加灵活方面,适应能力也会很是强。ide
func main() { var b bytes.Buffer fmt.Fprint(&b,"Hello World") fmt.Println(b.String()) }
以上就是一个使用接口的例子,咱们先看下fmt.Fprint
函数的实现。函数
func Fprint(w io.Writer, a ...interface{}) (n int, err error) { p := newPrinter() p.doPrint(a) n, err = w.Write(p.buf) p.free() return }
从上面的源代码中,咱们能够看到,fmt.Fprint
函数的第一个参数是io.Writer
这个接口,因此只要实现了这个接口的具体类型均可以做为参数传递给fmt.Fprint
函数。而bytes.Buffer
偏偏实现了io.Writer
接口,因此能够做为参数传递给fmt.Fprint
函数。布局
内部实现编码
咱们前面提过接口是用来定义行为类型的,它是抽象的,这些定义的行为不是由接口直接实现,而是经过方法由用户定义的类型实现。若是用户定义的类型,实现了接口类型声明的全部方法,那么这个用户定义的类型就实现了这个接口,因此这个用户定义类型的值就能够赋值给接口类型的值。spa
func main() { var b bytes.Buffer fmt.Fprint(&b, "Hello World") var w io.Writer w = &b fmt.Println(w) }
这个例子中,由于bytes.Buffer
实现了接口io.Writer
,因此咱们能够经过w = &b
赋值,这个赋值的操做会把定义类型的值存入接口类型的值。指针
赋值操做执行后,若是咱们对接口方法执行调用,实际上是调用存储的用户定义类型的对应方法,这里咱们能够把用户定义的类型称之为实体类型
。code
咱们能够定义不少类型,让它们实现一个接口,那么这些类型均可以赋值给这个接口。这时候接口方法的调用,其实就是对应实体类型
对应方法的调用,这就是多态。orm
func main() { var a animal var c cat a=c a.printInfo() //使用另一个类型赋值 var d dog a=d a.printInfo() } type animal interface { printInfo() } type cat int type dog int func (c cat) printInfo(){ fmt.Println("a cat") } func (d dog) printInfo(){ fmt.Println("a dog") }
以上例子演示了一个多态。咱们定义了一个接口animal
,而后定义地两种类型cat
和dog
实现了接口animal
。在使用的时候,分别把类型cat
的值c
、类型dog
的值d
赋值给接口animal
的值a
,而后分别执行a
的printInfo
方法,能够看到不一样的输出。接口
a cat a dog
咱们看下接口的值被赋值后,接口值内部的布局。接口的值是一个两个字长度的数据结构,第一个字包含一个指向内部表结构的指针,这个内部表里存储的有实体类型
的信息以及相关联的方法集;第二个字包含的是一个指向存储的实体类型
值的指针。因此接口的值结构实际上是两个指针,这也能够说明接口实际上是一个引用类型。
方法集
咱们都知道,若是要实现一个接口,必须实现这个接口提供的全部方法。可是实现方法的时候,咱们可使用指针接收者实现,也可使用值接收者实现,这二者是有区别的。下面咱们就好好分析下这二者的区别。
func main() { var c cat //值做为参数传递 invoke(c) } //须要一个animal接口做为参数 func invoke(a animal){ a.printInfo() } type animal interface { printInfo() } type cat int //值接收者实现animal接口 func (c cat) printInfo(){ fmt.Println("a cat") }
仍是原来的例子改改,增长一个invoke
函数,该函数接收一个animal
接口类型的参数,例子中传递参数的时候,也是以类型cat
的值c
传递的,运行程序能够正常执行。如今咱们稍微改造一下,使用类型cat
的指针&c
做为参数传递。
func main() { var c cat //指针做为参数传递 invoke(&c) }
只修改这一处,其余保持不变,咱们运行程序,发现也能够正常执行。经过这个例子咱们能够得出结论:实体类型以值接收者实现接口的时候,不论是实体类型的值,仍是实体类型值的指针,都实现了该接口。
下面咱们把接收者改成指针试试。
func main() { var c cat //值做为参数传递 invoke(c) } //须要一个animal接口做为参数 func invoke(a animal){ a.printInfo() } type animal interface { printInfo() } type cat int //指针接收者实现animal接口 func (c *cat) printInfo(){ fmt.Println("a cat") }
这个例子中把实现接口的接收者改成指针,可是传递参数的时候,咱们仍是按值进行传递,点击运行程序,会出现如下异常提示:
./main.go:10: cannot use c (type cat) as type animal in argument to invoke: cat does not implement animal (printInfo method has pointer receiver)
提示中已经很明显地告诉咱们,说cat
没有实现animal
接口,是由于printInfo
方法有一个指针接收者,因此cat
类型的值c
不能做为接口类型animal
传参使用。下面咱们再稍微修改下,改成以指针做为参数传递。
func main() { var c cat //指针做为参数传递 invoke(&c) }
其余都不变,只是把之前使用值的参数,改成使用指针做为参数,咱们再运行程序,就能够正常运行了。因而可知实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。
如今咱们总结下这两种规则,首先以方法接收者是值仍是指针的角度看。
Methods Receivers |
Values |
---|---|
(t T) |
T and *T |
(t *T) |
*T |
上面的表格能够解读为:若是是值接收者,实体类型的值和指针均可以实现对应的接口;若是是指针接收者,那么只有类型的指针可以实现对应的接口。
其次咱们以实体类型是值仍是指针的角度看。
Values |
Methods Receivers |
---|---|
T |
(t T) |
*T |
(t T) and (t *T) |
上面的表格能够解读为:类型的值只能实现值接收者的接口;指向类型的指针,既能够实现值接收者的接口,也能够实现指针接收者的接口。