Go语言之接口

简介:

接口是一种约定,它是一个抽象的类型,和我们见到的具体的类型如int、map、slice等不一样。具体的类型,我们可以知道它是什么,并且可以知道可以用它做什么;但是接口不一样,接口是抽象的,它只有一组接口方法,我们并不知道它的内部实现,所以我们不知道接口是什么,但是我们知道可以利用它提供的方法做什么。


抽象就是接口的优势,它不用和具体的实现细节绑定在一起,我们只需定义接口,告诉编码人员它可以做什么,这样我们可以把具体实现分开,编码就会更加灵活方面,适应能力也会非常强。


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函数。


内部实现


我们前面提过接口是用来定义行为类型的,它是抽象的,这些定义的行为不是由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型,实现了接口类型声明的所有方法,那么这个用户定义的类型就实现了这个接口,所以这个用户定义类型的值就可以赋值给接口类型的值。


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赋值,这个赋值的操作会把定义类型的值存入接口类型的值。


赋值操作执行后,如果我们对接口方法执行调用,其实是调用存储的用户定义类型的对应方法,这里我们可以把用户定义的类型称之为实体类型


我们可以定义很多类型,让它们实现一个接口,那么这些类型都可以赋值给这个接口。这时候接口方法的调用,其实就是对应实体类型对应方法的调用,这就是多态。


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,然后定义地两种类型catdog实现了接口animal。在使用的时候,分别把类型cat的值c、类型dog的值d赋值给接口animal的值a,然后分别执行aprintInfo方法,可以看到不同的输出。


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)


上面的表格可以解读为:类型的值只能实现值接收者的接口;指向类型的指针,既可以实现值接收者的接口,也可以实现指针接收者的接口。



本文转自 baby神 51CTO博客,原文链接:http://blog.51cto.com/babyshen/1921237,如需转载请自行联系原作者
相关文章
|
7月前
|
存储 Go
Go语言之接口与多态 -《Go语言实战指南》
Go 语言中的接口是实现多态的核心机制,通过一组方法签名定义行为。任何类型只要实现接口的所有方法即视为实现该接口,无需显式声明。本文从接口定义、使用、底层机制、组合、动态行为到工厂模式全面解析其特性与应用,帮助理解 Go 的面向接口编程思想及注意事项(如 `nil` 陷阱)。
228 22
|
2月前
|
Java 编译器 Go
【Golang】(5)Go基础的进阶知识!带你认识迭代器与类型以及声明并使用接口与泛型!
好烦好烦好烦!你是否还在为弄不懂Go中的泛型和接口而烦恼?是否还在苦恼思考迭代器的运行方式和意义?本篇文章将带你了解Go的接口与泛型,还有迭代器的使用,附送类型断言的解释
204 4
|
2月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
223 1
|
10月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
10月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
4月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
312 1
|
4月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
408 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
268 0
|
4月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
250 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
348 0