《GO语言圣经》读书笔记(六):接口

本节读书笔记对应原书第七章。

​ 接口是一种约定,它是一个抽象的类型,和我们见到的具体的类型(比如数字类型,我们知道可以进行算术操作)不一样。具体的类型,我们可以知道它是什么,并且可以知道可以用它做什么;但是接口不一样,接口是抽象的,接口不会暴露出它所代表代表对象的内部结构以及该对象的方法,所以我们不知道接口是什么,但是我们知道可以通过它提供的方法做什么。

​ 以下就是一个使用接口的例子,fmt.Printf()会把结果写到标准输出,该函数使用了另一个函数fmt.Fprintf进行封装,fmt.Fprintf对于它的结果会被怎么使用是完全不了解的。

func main() {
	var b bytes.Buffer
	fmt.Fprint(&b,"Hello World")
	fmt.Printf(b.String())
}

​ 看下Printffmt.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.ReaderWriterCloserReadWriter接口,所以它可读可写可关闭。

​ 下面的三个例子说明了接口指定是具体怎么做的,wio.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) 
} 

​ 从这里可以看出,具体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值