接口 - Go 语言学习笔记

什么是接口

Go接口是一种数据类型,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

在 go 语言中,接口有下面几个特点:

  • 可以包含0个或多个方法的签名
  • 只定义方法的签名,不包含实现
  • 实现接口不需要显式的声明,只需实现相应方法即可

接口的定义

利用关键字 interface 来定义一个接口,接口是一组方法的集合。
接口定义格式:

type 接口名称 interface{
    方法名称1(可能会用到的参数,可不传) 返回类型
    方法名称2(可能会用到的参数,可不传) 返回类型
    ...
}
复制代码

例如:

type People interface {
    Show(name string, age int) (id int, err error)  
    Set(name string, age int)
}
复制代码

接口的实现

在 go 语言中,接口的实现是隐式的,不需要显示声明实现了接口,当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。
接口的实现示例如下:

package main

import (
    "fmt"
)

type USB interface {
    Name() string
    Connect()
}

type PhoncConnecter struct {
    name string
}

// 因为以下方法 Name()、Connect() 与接口USB的方法相同,因此结构体PhoncConnecter实现了USB接口

func (pc PhoncConnecter) Name() string {
    return pc.name
}

func (pc PhoncConnecter) Connect() {
    fmt.Println(pc.name)
}

func main() {
    // 第一种直接在声明结构时赋值
    var a USB   // 声明一个接口类型的变量
    a = PhoncConnecter{"PhoneC"}    // 因为 PhoncConnecter 实现了接口 USB,所以可以将该类型的实例赋给接口的变量
    a.Connect()

    // 第二种,先给结构赋值后在将值给接口去调用
    var b = PhoncConnecter{}
    b.name = "b"
    var c USB   // 声明一个接口类型的变量
    c = b
    c.Connect()
}
复制代码

输出为:

PhoneC
b
复制代码

以上示例定义了USB的接口,并声明了两个方法 Name()、Connect(),接着定义了PhoncConnecter结构并声明了一个name的变量,通过方法特性,对结构也同样声明了两个方法Name()、Connect()。在GO中,只要实现了接口中定义的方法,默认就代表类似于其它语言中的继承,继承了那个接口,所以在main函数中,就可以通过声明接口和结构进行相对应的操作,从而达到代码重复使用。

接口的值类型

1. 接口的内部表现

一个接口可以被认为是由一个元组(类型,值)在内部表示的。type是接口的基础具体类型,value是具体类型的值。

接口是一系列接口的集合,是一种抽象数据类型,接口变量可以引用任何实现了接口的全部方法的具体数据类型的值。

接口变量存储了两部分信息,一个是分配给接口变量的具体值(接口实现者的值),一个是值的类型的描述器(接口实现者的类型),形式是(value, concrete type),而不是(value, interface type)。

package main

import (
	"fmt"
)

type Test interface {
	Tester()
}

type MyFloat float64

func (m MyFloat) Tester() {
	fmt.Println(m)
}

func describe(t Test) {
	fmt.Printf("Interface 类型: %T ,  值: %v\n", t, t)
}

func main() {
	var t Test
	f := MyFloat(89.7)
	t = f
	describe(t)
	t.Tester()
}
复制代码

运行结果:

Interface 类型: main.MyFloat ,  值: 89.7
89.7
复制代码

2. 空接口

空接口就是不包含任何方法的接口,它表示为 interface {}。正因为如此,所有的类型都实现了空接口。

虽然空接口起不到任何作用,但是空接口在需要存储任何类型数值的时候非常有用,因为空接口可以存储任意类型的数据。

package main

import (
	"fmt"
)

func describe(i interface{}) {
	fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main() {
    // 任何类型的变量传入都可以
    s := "Hello World"
    i := 55
    strt := struct {
        name string
    }{
        name: "Naveen R",
    }
    describe(s)
    describe(i)
    describe(strt)
}
复制代码

运行结果:

Type = string, value = Hello World
Type = int, value = 55
Type = struct { name string }, value = {Naveen R}
复制代码

3. 类型断言

interface{} 可用于向函数传递任意类型的变量,但对于函数内部,该变量仍然为 interface{} 类型(空接口类型),而不是传入的实参类型。

接口类型向普通类型的转换称为类型断言(运行期确定),类型断言用于提取接口的基础值,语法:i.(T)

func printArray(arr interface{}){
    // arr 是空接口,不是数组类型,报错
    for _, v: = range arr{
        fmt.Print(v," ")
    }
  fmt.Println()
}
复制代码

可以通过类型断言将接口类型转换为切片类型

func printArray(arr interface{}){
    // 通过断言实现类型转换
    a,_ := arr.([]int)
    for _,v:=range a{
        fmt.Println(v, " ")
    }
    fmt.Println()
}
复制代码

在使用类型断言时,最好判断断言是否成功

b,ok := a.(T)
if ok {
    ...
}
复制代码

4. 类型判断

类型判断的语法类似于类型断言,在类型断言的语法i.(type)中,类型 type 应该由类型转换的关键字 type 替换。 类型断言可以配合switch语句进行判断:

package main

import (  
    "fmt"
)

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("String: %s\n", i.(string))
    case int:
        fmt.Printf("Int: %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
func main() {  
    findType("Naveen")
    findType(77)
    findType(89.98)
}
复制代码

运行结果:

String: Naveen
Int: 77
Unknown type
复制代码

接口的使用

1. 内嵌接口

一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
比如接口 File 包含了 ReadWrite 和 Lock 的所有方法,它还额外有一个 Close() 方法。

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type Lock interface {
    Lock()
    Unlock()
}

type File interface {
    ReadWrite
    Lock
    Close()
}
复制代码

2. 接口变量赋值

可以将一个实现接口的对象实例赋值给接口,也可以将另外一个接口赋值给接口。

  • 通过对象实例赋值
    如果一个类型实现了某个接口,可以将类型的实例赋值给接口变量

  • 通过接口赋值
    接口A和接口B,如果接口A是接口B的子集,B类型的变量赋给A类型的变量

package main

import (
	"bytes"
	"io"
	"os"
	// "time"
)

func main() {
	// File、bytes.Buffer 实现了Writer
	var w io.Writer  // A
	w = os.Stdout
	w = new (bytes.Buffer)

	// w = time.Second  // error, 因为time.Second没有实现了Writer

	var rwc io.ReadWriteCloser  // B
	rwc = os.Stdout // File 实现了 io.ReadWriteCloser 接口

	w = rwc
}
复制代码

3. 用接口实现多态

go 语言多态是将实现接口的不同类型的实例赋给接口变量,让接口的方法拥有不同的行为。

package main

import "fmt"

type MyGreeting interface {
	Greet(name string) string
}

type EnglishGreeting struct {

}

type ChinesGreeting struct {

}

func (this *EnglishGreeting) Greet(name string) string {
	return "Hello " + name
}

func (this *ChinesGreeting) Greet(name string) string {
	return "你好 " + name
}

func main() {
	var greet MyGreeting = new(EnglishGreeting)
	fmt.Println(greet.Greet("Bill"))

	greet = new(ChinesGreeting)
	fmt.Println(greet.Greet("Bill"))
}
复制代码

运行结果:

Hello Bill
你好 Bill
复制代码

转载于:https://juejin.im/post/5cf8d12851882556174ddd0d

猜你喜欢

转载自blog.csdn.net/weixin_34310369/article/details/93181606