博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
go语言之接口
阅读量:6452 次
发布时间:2019-06-23

本文共 9781 字,大约阅读时间需要 32 分钟。

一:接口的基本概念

1 接口声明

接口字面量,接口命名类型,接口声明使用interface关键字。

  1)接口字面量类型声明语法如下:

interface{      methodSignature1          methodSignature1    }

    2)接口命名类型使用type关键字声明

type   interfaceName interface {       MethodSignature1       MethodSignature2}
接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以是二者的混合。接口支持嵌入匿名接口宇段,就是一个接口定义里面可以包括其他接口, Go编译器会自动进行展开 理,
type Reader interface {     Read(p []byte ) (n int , err error) }type Writer interface {     Write(p []byte) (n int, err error) }
type ReadWriter interface {     Reader     Writer }type ReadWr ter interface {     Reader     Wr te(p []byte) (n int, err error) }type ReadWriter interface {     Read(p []byte) (n int, err error)     Write(p []byte) (n nt err error)}

  3)方法声明

MethodName (InputTypeList)OutputTypeList

     4)声明新接口类型的特点

(I)接口的命名一般以“er ”结尾(2)接口定义的内部方法声明不需要 func 引导。(3)在接口定义中,只有方法声明没有方法实现。

  5) 接口的定义与实现

package mainimport (    "fmt")type Humaner interface {    //方法    Say()}//学生结构体type Student struct {    name string    score int}func (s*Student) Say()  {    fmt.Println("Student[%s,%d]瞌睡不断\n",s.name,s.score)  }type Teacher struct {    name string    group string}func (t *Teacher) Say()  {    fmt.Println("Teacher[%s,%s] 诲人不倦\n",t.name,t.group)}//自定义类型type  Mystr stringfunc (str Mystr) Say() {    fmt.Println("Mystr[%s] 统治醒醒,还有个bug\n",str)}//参数为接口类型func Whosay(i Humaner)  {    i.Say()}func main(){    s :=&Student{
"学生",88} t :=&Teacher{
"老师","GO语言"} var tmp Mystr="字符串" s.Say() t.Say() tmp.Say() //多态,条用同一接口不同的表现 Whosay(s) Whosay(t) Whosay(tmp) //make() x :=make([]Humaner,3) x[0],x[1],x[2] = s,t,tmp for _,value :=range x{ value.Say() }}

  接口的继承

package mainimport "fmt"//定义接口type Humaner interface {    //方法    Say()}type Personer interface {    //相当于写了say() 方法的继承    Humaner    //唱歌    Sing(lyrics string)}type Student struct {    name string    score int}func (s *Student) Say() {    fmt.Printf("Student[%s,%d] 瞌睡不断\n",s.name,s.score)   //Student[学生,80] 瞌睡不断}func (s *Student) Sing(lyrics string){    fmt.Printf("Student sing[%s]!!\n",lyrics)                  //Student sing[葫芦娃]!!}func main()  {    s := &Student{
"学生",80} //调Personer方法 var p Personer p = s p.Say() p.Sing("葫芦娃")}

2 接口初始化

单纯地声明一个接口变量没有任何意义,接口只有被初始化为具体的类型时才有意义。接口作为 一个胶水层或抽象层,起到抽象和适配的作用 。没有初始化的接口变量,其默认值是 nil。

3 接口绑定具体类型的实例的过程称为接口初始化。接口变量支持两种直接初始化方法

  1)实例赋值接口

如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体类型实现了接口,可以将该具体类型的实例直接赋值给接口类型的变 ,此时编译器会进行静态的类型检查。接口被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语义。

  2)接口变量赋值接口变量

已经初始化的接口类型变量a直接赋值给另一种接口变量b ,要求b的方法集是a的方法即的子集 此时 Go 编译器会在编译时进行方法集静态检查 这个过程也是接口初始化的一种方式,此时接口变量 绑定的具体实例是接口变量 绑定的具体实例的副本。
file ,_ := os .OpenFile (” notes.txt”, os.O_RDWR |os.O CREATE , 0755 ) var rw io .ReadWriter = file //io.ReadWriter 口可以直接赋位给 io.Writer接口变量var  w  o.Writer = rw

4 接口方法的调用

接口方法调用和普通的函数调用是有区别的。接口方法调用的最终地址是在运行期决定的,将具体类型变量赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,实际上是间接地调用实例的方法。接口方法调用不是 种直接的调用,有 定的运行时开销

  直接调用禾初始化的接口变 的方法会产生 panic 。

package maintype printer interface {    Print()}type S struct {}func (s S) Print()  {    println("print")}func main()  {    var i  printer    //没有初始化的接口调用其他方法会产生panic    //必须初始化    i = S{}    i.Print()}

5 接口动态类型和静态类型

  1)动态类型

接口绑定的具体实例的类型称为接口的动态类型。接口可以绑定不同类型的实例,所以接口的动态类型是随着其绑定的不同类型实例而发生变化的。

  2) 静态类型

接口被定义时, 其类型就已经被确定 这个类型 接口的静态类型。接口的静态类型在其定义 就被确定,静态类型的本质特征就是接口的方法签名集合。两个接口如果方法签名集合相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。原因是 Go 编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。

二: 接口运算

1 语法:

i.(TypeNname)
i必须是接口变 ,如果是具体类型变量,则编译器会报 on interface type xxx on left, TypeNname 可以是接口类型名,也可以是具体类型名。

2 接口查询的两层含义

(1)如果 TypeNname 是一个具体类型名,则类型断言用于判断接口变量 绑定的实例类型是否就是具体类型 TypeNname(2)如果 TypeName 是一个接口类型名,则类型断言用于判断接口变量 绑定的实例类型是否同时实现了 TypeName 接口。

3 接口断言的两种语法表现

  直接赋值模式

o := i.(TypeName)

  语义分析:

(1) TypeNam 是具体类型名,此时如果接 绑定的实例类型就是具体类型 TypeName,变量 。的类型就是 TypeName 变量。的值就是接口绑定的实例值的副本(当然实例可能是指针值,那就是指针值的副本)(2) TypeName 是接口类型名 如果接口i绑定的实例类型满足接口类型 TypeName ,则变量o的类型就是接口类型 TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本〉。(3)如果上述两种情况都不满足, 则程序抛 panic

  示例

package mainimport "fmt"type Inter interface {    Ping()    Pang()}type Anter interface {    Inter    String()}type St struct {    Name string}func (St) Ping() {    println("ping")}func (*St) Pang()  {    println("pang")}func main()  {    st := &St{
"andes"} var i interface{}=st //判断i绑定的实例是否实现了接口类型Inter o :=i.(Inter) o.Ping() o.Pang() //如下语句会引发panic,因为i没有实现接口Anter //p :=i.(Anter) //p.String() //判断 i绑定的实例是否就是具体类型St s := i.(*St) fmt.Printf("%s",s.Name)}

4  comma,ok 表达模式如下

if o,ok :=i.(TypeName);ok{}

  语法分析

(1)TypeName是具体类型名,此时如果接口i绑定的实例类型就是具体类型TypeName,则ok为true变量。变量o的类型就是TypeName,变量o的值就是接口绑定的实例值的副本(当然实例可能是指针值,那就是指针值的副本)(2)TypeName是接口类型名,此时如果接口i绑定的实例类型满足接口类型TypeName,则ok为true,变量o的类型就是接口类型 TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本)。(3)如果上述两个都不满足,则 ok为 false 变量o是TypeName 类型的“零值”,此种条件分支下程序逻辑不应该再去引用o,因为此时的o没有意义

  示例:

package mainimport (    "fmt")type Inter interface {    Ping()    Pang()}type Anter interface {    Inter    String()}type St struct {    Name string}func (St) Ping(){    println("ping")}func (*St) Pang(){    println("pang")}func main(){    st :=  &St{
"andes"} var i interface{} = st //判断i绑定的实例是否实现了接口类型Inter if o,ok := i.(Inter);ok{ o.Ping() //ping o.Pang() //pang } if p,ok := i.(Anter);ok{ //i没有实现接口Anter,所以程序不会执行到这里 p.String() } //判断i 绑定的实例是否就是具体类型St if s,ok := i.(*St);ok{ fmt.Printf("%s",s.Name) //andes }}

5  类型查询

  语法格式:

switch v :=工. (type) { case typel : xx xx case type2 : xx xx default : xx xx

  语义分析:

语义:1 查询一个接口变量底层绑定的底层变量的具体类型是什么,2 查询接口变量绑定的底层变量是否实现了其他接口

  1)i 必须是接口类型

描述:具体类型实例的类型是静态的 在类型声明后就不再变化,所 具体类型的变量不存在类型查询 类型查询一定是对一个接口变量进行操作。也 就是说,上文中的i必须是接口变如果 是未初始 接口变量,则的值是nil 。
var i io.Readerswitch v := i.(type) { //此处i是为未初始化的接口变量,所以v为nil    case nil :         fmt.Printf( " %T\n ”,v )  //
default : fmt.Printf (”default”) }

( 2 ) case 字句后面可 m~ 非接口类型名,也可以跟接口类型名,匹配是按照 case 子句的

顺序进行的。

如果 case 后面是一个接口类型名,且接口变量 绑定的实例类型实现了该接口类型的方法,则匹配成功,v的类型是接口类型,v底层绑定的实例是 绑定具体类型实例的副本。

  示例:

package mainimport (    "io"    "log"    "os")func main()  {    f,err := os.OpenFile ("notes.txt",os.O_RDWR|os.O_CREATE, 0755)    if err != nil {        log.Fatal(err)    }    defer f.Close()    var i io.Reader = f    switch v :=i.(type) {        //i的绑定的实例是*osFile类型,实现 io.ReadWriter接口,所以case匹配成功        case io.ReadWriter:            //v是io.ReadWriter 接口类型,所以可以调用Write方法            v.Write( []byte ("io.ReadWriter\n" ))        //由于上一个case 已经匹配,就算这个case 也匹配,也不会走到这里        case *os.File:            v.Write ([]byte ("*os.File\n"))            v.Sync ()        default:        return    }}
如果case后面跟着多个类型,使用逗号分隔,接口变量i绑定的实例类型只要和其中一个类型匹配,则直接使用o赋值给 v,相当于v := o 这个语法有点奇怪,按理说编译器不应该允许这种操作,语言实现者可能想让 type switch 语句和普通的 sw itch 语句保持一样的语法规则,允许发生这种情况。
package mainimport (    "fmt"    "io"    "log"    "os")func main(){    f,err := os.OpenFile("notes1.txt",os.O_RDWR|os.O_CREATE,0756)    if err != nil {        log.Fatal(err)    }    defer f.Close()    var i  io.Reader = f    switch v  := i.(type) {    //多个类型,f满足其中任何一个就算匹配    case *os.File,io.ReadWriter:        //此时相当于执行v :=i ,v和i是等价的,使用v没有意义        if v==i{            fmt.Println(true) //true        }    default:        return    }}

6 标准库的使用

  格式:

switch i := i.(type) { }

  类型查询和类型断言

(1)类型查询和类型断言具有相同的语义,只是语法格式不同。 二者都能判断接口变量绑定的实例的具体类型,以及判断接口变量绑定的实例是否满足另一个接口类型。(2)类型查询使用 case 字句一次判断多个类型,类型断言一次只能判断一个类型,当然类型断言也可以使用 if else if 语句达到同样的效果

7  接口优点和使用形式

  接口优点

(1)解祸:复杂系统进行垂 和水平的分割是常用的设计手段,在层与层之间使用接口进行抽象和解辑是 种好的编程策略 Go 的非侵入式的接口使层与层之间的代码更加干净,具体类型和实现的接口之间不需要显式声明,增加了接口使用的自由度
(2)实现泛型:由于现阶段Go语言还不支持泛型,使用空接口作为函数或方法参数能够用在需要泛型的场景中

  接口的使用形式

(3)作为结构 嵌字段。(2)作为函数或方法的形参。(3)作为函数或方法的返回值。(4)作为其他接口定义的嵌入宇段。

三: 空接口

概述:没有任何方法的接口,我们称之为空接 。空接口表示为 interface{}
用途:空接口和泛型Go 语言没有泛型, 如果一个函数需要接收任意类型的参数, 则参数类型可以使用空接口,这是弥补没有泛型的一种手段//典型的就是 fmt 标准 里面的 print 函数func Fprint (w io.Writer, a . . . interface(}) (n int, err error) 空接口和反射空接口是反射实现 基础 反射库就是将相关具体的类型转换并赋值给空接 后才去处理,

1 空接口和nil

空接口不是真的为空,接口有类型和值两 概念

示例

package mainimport (    "fmt")type Inter interface {    Ping()    Pang()}type St struct {}func (St) Ping(){    println("ping")}func (*St) Pang(){    println("pang")     //pamg}func main(){    var st *St = nil    var it Inter = st    fmt.Printf("%p\n",st)  //0x0    fmt.Printf("%p\n",it)  //0x0    if it !=nil {        it.Pang()        //下面的语句会导致 panic        //方法转换为函数调用,第 一个参数是St类型,由于 St是nil ,无法获取指针所指的        //对象佳,所以导致 panic        //it.Ping    }}

    comma-ok断言

package mainimport (    "fmt")//空接口type Element interface {}type Person struct {    name string    age int}func main()  {    //3容量的切片    list := make([]Element,3)    list[0] = 1            //int    list[1]="Hello"      //string    list[2] = Person{
"zhangsan",18} for index,element := range list { //类型断言: value,ok =element,(T) if value,ok :=element.(int);ok { fmt.Printf("list[%d]是int类型,值是%d\n",index,value) //list[0]是int类型,值是1 }else if value,ok := element.(string);ok { fmt.Printf("list[%d]是string类型,值是%s\n",index,value) //list[1]是string类型,值是Hello }else { fmt.Printf("list[%d]是其他类型\n",index) //list[2]是其他类型 } }}

  switch 接口测试

package mainimport "fmt"//空接口type Element interface{}type Person struct {    name string    age  int}func main() {    list := make([]Element, 3)    list[0] = 1       //int    list[1] = "Hello" //string    list[2] = Person{
"zhangsan", 18} for index,element := range list{ switch value := element.(type) { case int : fmt.Printf("list[%d]是int类型,值是%d\n",index,value) case string: fmt.Printf("list[%d]是string类型,值是%s\n",index,value) default: fmt.Printf("list[%d]是其他类型\n",index) } }}

 

 

四: 接口的内部实现(这个涉及底层很多东西,我不会)

 

转载于:https://www.cnblogs.com/liucsxiaoxiaobai/p/10806634.html

你可能感兴趣的文章
MySQL my.cnf参数配置优化详解
查看>>
HDU/HDOJ 2102 A计划 广度优先搜索BFS
查看>>
JavaNIO基础02-缓存区基础
查看>>
阿里 Blink 正式开源,重要优化点解读
查看>>
日本开设无人机专业,打造无人机“人才市场”
查看>>
c/c++通用内存泄漏检测框架GMFD(General Memory Fault Detection Framework)
查看>>
异构计算:PC的“动车组”
查看>>
MariaDB 10之TokuDB存储引擎
查看>>
Flex与.NET互操作(一):基于Socket的网络连接
查看>>
WPF中Style的使用
查看>>
组策略设置服务器安全-----不显示最后的用户名
查看>>
慢查询日志的另外一种方法
查看>>
引发CPU占用率快100%的可能原因
查看>>
公司今年的一道校招笔试题--五猴分桃
查看>>
[数据恢复答疑]RAID5有一块硬盘离线后,为什么不建议马上做REBUILD?
查看>>
Exchange 2013 EAC之管理员重置普通用户密码
查看>>
三线跑酷例子BlocksRun的技术点
查看>>
如何应对DDOS网络攻击
查看>>
新闻奖颁给了一个写稿机器人(来自新华社)
查看>>
Android应用程序在新的进程中启动新的Activity的方法和过程分析
查看>>