本节读书笔记对应原书第七章。
接口是一种约定,它是一个抽象的类型,和我们见到的具体的类型(比如数字类型,我们知道可以进行算术操作)不一样。具体的类型,我们可以知道它是什么,并且可以知道可以用它做什么;但是接口不一样,接口是抽象的,接口不会暴露出它所代表代表对象的内部结构以及该对象的方法,所以我们不知道接口是什么,但是我们知道可以通过它提供的方法做什么。
以下就是一个使用接口的例子,fmt.Printf()
会把结果写到标准输出,该函数使用了另一个函数fmt.Fprintf
进行封装,fmt.Fprintf
对于它的结果会被怎么使用是完全不了解的。
func main() {
var b bytes.Buffer
fmt.Fprint(&b,"Hello World")
fmt.Printf(b.String())
}
看下Printf
和 fmt.Fprint
函数的实现。
func Printf(format string, args ...interface{}) (int, error) {
return Fprintf(os.Stdout, format, args...)
}
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
}
从上面的源代码中,可以看到Printf
内部的确封装了Fprintf
函数,此外fmt.Fprint
函数中前缀F
表示文件,还表示格式化输出结果应该被写入到第一个参数提供的文件中。第一个参数是io.Writer
这个接口,id.Writer
定义了改函数和函数调用者之间的约定,保证了Fprintf
接受任何满足io.Writer
接口的值都可以工作,而bytes.Buffer
恰恰实现了io.Writer
接口,所以可以作为参数传递给fmt.Fprint
函数。
接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。如果一个类型拥有了一个接口需要的所有方法,那么这个类型就实现了这个接口。
特别的说明,一般把一个具体的类型描述成一个特定的接口类型,比如说*bytes.Buffer是io.Writer, *os.Files是io.ReadWriter。
接口指定规则:一个类型属于某个接口只要这个类型实现这个接口就可以了。比如说,*os.File
类型实现了io.Reader
、Writer
、Closer
和ReadWriter
接口,所以它可读可写可关闭。
下面的三个例子说明了接口指定是具体怎么做的,w
是io.Writer
接口类型,os.Stdout
拥有Write
方法,所以os.Stdout
属于io.Writer
接口。time.Second
类型就没有Write
方法,所以会出现compile error
错误。
var w io.Writer
w=os.Stdout
w=new(bytes.Buffer)
w=time.Second
**PS:**不能对空接口interface{}持有的值操作,因为interface{}
没有任何方法
对于每一个命名过的具体类型T,它一些方法的接收者是类型T本身,然而另一些则是一个T的指针。在T类型的参数上调用一个T的方法是合法的,只要这个参数是一个变量,编译器会隐式的获取它的地址,但这仅仅是一个语法糖,T类型的值不拥有所有*T指针的方法,那这样他就可能只实现更少的接口。
举个例子,定义一个animal
接口,其中cat
实现了animal
接口中的printInfo
方法,根据如果一个类型拥有了一个接口需要的所有方法,那么这个类型就实现了这个接口,我们知道cat实现了animal的接口。
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
作为参数传递,可以发现程序也是可以正常执行的。参数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")
}
报错如下:
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
传参使用,所以这就是为啥书里面说T类型的值不拥有所有*T指针的方法。
那改为以指针作为参数传递应该就可以了,试下发现可以正常运行了。
func main() {
var c cat
//指针作为参数传递
invoke(&c)
}
从这里可以看出,具体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。