首页 > 其他分享 >Go 语言系列23:接口

Go 语言系列23:接口

时间:2022-11-25 13:39:42浏览次数:44  
标签:23 fmt 接口 Cat interface func Go type

在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型定义了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。


Go 语言系列23:接口_赋值

接口的定义

Go 语言系列23:接口_多态_02


使用 ​​type​​ 关键字可以定义接口:

type interface_name interface {
method()
}

Go 语言系列23:接口_赋值

接口的实现

Go 语言系列23:接口_多态_02


创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:

package main

import "fmt"

type Usb interface {
link()
}

type Computer struct {
name string
}

func (computer Computer) link() {
fmt.Println("电脑 USB 接口连接到 U 盘")
}

func main() {
myComputer := Computer{"菜籽的电脑"}
myComputer.link()
}

上面的程序定义了一个名为 ​​Usb​​​ 的接口,接口中有未实现的方法 ​​link()​​​ ,这里还定义了名为 ​​Computer​​​ 的结构体,其绑定了方法 ​​link()​​​ ,也就隐式实现了 ​​Usb​​​ 接口,实现的内容是打印 ​​电脑 USB 接口连接到 U 盘​​ 语句,运行该程序输出如下:

电脑 USB 接口连接到 U 盘

上面的例子使用了值接受者实现接口,下面的例子使用了指针接受者实现接口。

package main

import "fmt"

type Describer interface {
Describe()
}

type Person struct {
name string
age int
}

func (person Person) Describe() {
fmt.Printf("%s is %d years old\n", person.name, person.age)
}

type Date struct {
year int
month int
day int
}

func (date *Date) Describe() {
fmt.Printf("Today is %d-%d-%d\n", date.year, date.month, date.day)
}

func main() {
var d1 Describer
var d2 Describer

person1 := Person{"John", 30}
d1 = person1
d1.Describe()

person2 := Person{"Mary", 25}
d1 = &person2
d1.Describe()

date := Date{2022, 1, 1}
// d2 = date // error
d2 = &date
d2.Describe()
}

该程序定义了结构体 ​​Person​​​ ,使用其作为值接受者实现 ​​Describer​​​ 接口。​​person1​​​ 的类型为 ​​Person​​​ , ​​person1​​​ 赋值给 ​​d1​​​ ,由于 ​​Person​​​ 实现了接口变量 ​​d1​​​ 所以会有输出。而接下来 ​​d1​​​ 又被赋值为 ​​&person2​​​ ,同样有输出。接下来的结构体 ​​Date​​​ 使用指针接受者实现 ​​Describer​​​ 接口。​​date​​​ 的类型为 ​​Date​​​ , ​​d2​​​ 被赋值为 ​​&date​​​ ,所以会有输出。但如果把 ​​d2​​​ 赋值为 ​​date​​​ 会报错,对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此对于编译器无法自动获取 ​​date​​ 的地址,于是程序报错。运行该程序输出如下:

John is 30 years old
Mary is 25 years old
Today is 2022-1-1

Go 语言系列23:接口_赋值

接口实现多态

Go 语言系列23:接口_多态_02


使用接口可以实现多态,例如下面的程序,定义了名为 ​​Animal​​​ 的接口,接口中有方法 ​​bark()​​​ ,也就是说动物会发出不同的叫声。程序中还定义了结构体 ​​Cat​​​ 和 ​​Dog​​​ ,分别实现了 ​​Animal​​​ 接口,猫的叫声为 ​​喵喵喵...​​​ 而狗的叫声为 ​​汪汪汪...​​ ,利用的接口实现了不同的功能,这就是多态。

package main

import "fmt"

type Animal interface {
bark()
}

type Cat struct {
name string
}

type Dog struct {
name string
}

func (cat Cat) bark() {
fmt.Println("喵喵喵...")
}

func (dog Dog) bark() {
fmt.Println("汪汪汪...")
}

func main() {
myCat := Cat{"哆啦A梦"}
myDog := Dog{"史努比"}
myCat.bark() // 喵喵喵...
myDog.bark() // 汪汪汪...
}

Go 语言系列23:接口_赋值

接口的内部表示

Go 语言系列23:接口_多态_02


可以把接口看作内部的一个元组 ​​(type, value)​​​。​​type​​​ 是接口底层的具体类型(Concrete Type),而 ​​value​​ 是具体类型的值。

package main

import "fmt"

type Animal interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) bark() {
fmt.Println("喵喵喵...")
}

func describe(animal Animal) {
fmt.Printf("Interface type: %T\nInterface value: %v\n", animal, animal)
}

func main() {
var animal Animal
myCat := Cat{"哆啦A梦"}
animal = myCat
describe(animal)
animal.bark()
}

在上面的程序中,定义了 ​​Animal​​​ 接口,其中有 ​​bark()​​​ 方法,结构体 ​​Cat​​​ 实现了该接口。使用 ​​animal = myCat​​​ 语句我们把 ​​myCat​​​ ( ​​Cat​​​ 类型)赋值给了 ​​animal​​​ ( ​​Animal​​​ 类型),现在打印出 ​​animal​​​ 的具体类型为 ​​Cat​​​ ,而 ​​Cat​​​ 的值为 ​​{哆啦A梦}​​ 。运行该程序输出如下:

Interface type: main.Cat
Interface value: {哆啦A梦}
喵喵喵...

Go 语言系列23:接口_赋值

空接口

Go 语言系列23:接口_多态_02


空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 ​​interface{}​​ 。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:

package main

import "fmt"

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

func main() {
str := "Let's go"
describe(str)
num := 3.14
describe(num)
}

上面的程序中我们定义了函数 ​​describe​​ 使用空接口作为参数,所以可以给这个函数传递任何类型的参数,运行该程序输出如下:

Type: string, value: Let's go
Type: float64, value: 3.14

通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 ​​nil​​ :

package main

import "fmt"

func main() {
var in interface{}
fmt.Printf("Type: %T, Value: %v", in, in)
// Type: <nil>, Value: <nil>
}

除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。

直接使用 ​​interface{}​​ 作为类型声明一个实例,这个实例就能承载任何类型的值:

package main

import "fmt"

func main() {
var in interface{}

in = "Let's go"
fmt.Println(in) // Let's go

in = 3.14
fmt.Println(in) // 3.14
}

我们也可以定义一个接收任何类型的 ​​array​​​ 、 ​​slice​​​ 、 ​​map​​​ 、 ​​strcut​​ 。例如:

package main

import "fmt"

func main() {
x := make([]interface{}, 3)
x[0] = "Let's go"
x[1] = 3.14
x[2] = []int{1, 2, 3}
for _, value := range x {
fmt.Println(value)
}
}

运行该程序输出如下:

Let's go
3.14
[1 2 3]

空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。

package main

func main() {
var num = 1
var in interface{} = num
var str string = in // error
}

当空接口承载数组和切片后,该对象无法再进行切片。

package main

import "fmt"

func main() {
var slice = []int{1, 2, 3}

var in interface{} = slice

var newSlice = in[1:2] // error
fmt.Println(newSlice)
}

Go 语言系列23:接口_赋值

类型断言

Go 语言系列23:接口_多态_02


类型断言用于提取接口的底层值(Underlying Value)。使用 ​​interface.(Type)​​​ 可以获取接口的底层值,其中接口 ​​interface​​​ 的具体类型是 ​​Type​​ 。

package main

import "fmt"

func assert(in interface{}) {
value, ok := in.(int)
fmt.Println(value, ok)
}

func main() {
var x interface{} = 3
assert(x)
var y interface{} = "Let's go"
assert(y)
}

运行上面的程序输出如下:

3 true
0 false

Go 语言系列23:接口_赋值

类型选择

Go 语言系列23:接口_多态_02


类型选择用于将接口的具体类型与 ​​case​​​ 语句所指定的类型进行比较。它其实就是一个 ​​switch​​​ 语句,但在 ​​switch​​​ 后面跟的是 ​​in.(type)​​​ ,并且每个 ​​case​​ 后面跟的是类型。

package main

import "fmt"

func getTypeValue(in interface{}) {
switch in.(type) {
case int:
fmt.Printf("Type: int, Value: %d\n", in.(int))
case string:
fmt.Printf("Type: string, Value: %s\n", in.(string))
default:
fmt.Printf("Unknown type\n")
}
}

func main() {
getTypeValue(3)
getTypeValue("abc")
getTypeValue(true)
}

运行上面的程序输出如下:

Type: int, Value: 3
Type: string, Value: abc
Unknown type

Go 语言系列23:接口_赋值

实现多个接口

Go 语言系列23:接口_多态_02


类型或者结构体可以实现多个接口,例如:

package main

import "fmt"

type Describer interface {
Describe()
}

type Animal interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) Describe() {
fmt.Printf("Cat name: %s\n", cat.name)
}

func (cat Cat) bark() {
fmt.Printf("喵喵喵...\n")
}

func main() {
myCat := Cat{"哆啦A梦"}
myCat.Describe()
myCat.bark()
}

上面的程序定义了两个接口,结构体 ​​Cat​​ 分别实现了这两个接口,运行该程序输出如下:

Cat name: 哆啦A梦
喵喵喵...

Go 语言系列23:接口_赋值

接口的嵌套

Go 语言系列23:接口_多态_02


虽然在 Go 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:

package main

import "fmt"

type Animal interface {
Describer
Bark
}

type Describer interface {
Describe()
}

type Bark interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) Describe() {
fmt.Printf("Cat name: %s\n", cat.name)
}

func (cat Cat) bark() {
fmt.Printf("喵喵喵...\n")
}

func main() {
myCat := Cat{"哆啦A梦"}
myCat.Describe()
myCat.bark()
}

​Cat​​​ 结构体实现了 ​​Animal​​ 接口。运行该程序输出如下:

Cat name: 哆啦A梦
喵喵喵...

参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.




标签:23,fmt,接口,Cat,interface,func,Go,type
From: https://blog.51cto.com/u_15891283/5886553

相关文章

  • Go 语言系列22:方法
    方法其实在之前结构体那边简单讲过,方法其实就是一个函数,在​​func​​这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型......
  • Go 语言系列24:go 协程
    Go协程是与其他函数或方法一起并发运行的函数或方法。Go协程可以看作是轻量级线程。与线程相比,创建一个Go协程的成本很小。因此在Go应用中,常常会看到有数以千计的Go......
  • Go 语言系列21:goto 无条件跳转
    在Go语言中保留​​goto​​​这点我确实没想到,毕竟很多人不建议使用​​goto​​​语句。​​goto​​后面接的是标签,表示下一步要执行哪里的代码。gotolabel...la......
  • LeetCode 232.用栈实现队列(简单)
    题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(​​push​​​、​​pop​​​、​​peek​​​、​​empty​​):实现​​MyQueu......
  • one preparation of algorithms in short text clustering
    文本聚类算法文本聚类一般步骤文本表示(TextRepresentation)把文档表示成聚类算法可以处理的形式。聚类算法选择或设计(ClusteringAlgorithms)算法的选择,往往伴随着相......
  • Go语言错误总结(三)
    15、Strings无法修改尝试使用索引操作来更新字符串变量中的单个字符将会失败。string是只读的byteslice(和一些额外的属性)。如果你确实需要更新一个字符串,那么使用byteslic......
  • Go语言错误总结(二)
    8、使用“nil”SlicesandMaps在一个nil的slice中添加元素是没问题的,但对一个map做同样的事将会生成一个运行时的panic。正确代码:packagemainfuncmain(){vars[]i......
  • Go语言错误总结(五)
    29、未导出的结构体不会被编码以小写字母开头的结构体将不会被(json、xml、gob等)编码,因此当你编码这些未导出的结构体时,你将会得到零值。packagemainimport("encoding/......
  • Go语言错误总结(四)
    22、内建的数据结构操作不是同步的即使Go本身有很多特性来支持并发,并发安全的数据集合并不是其中之一,确保数据集合以原子的方式更新是你的职责。Goroutines和channels是实现......
  • Laravel:接口即契约
    强类型与鸭子类型在之前的章节里,我们讨论了依赖注入的基础知识:什么是依赖注入;如何实现依赖注入;依赖注入有什么好处。之前的例子中也模拟了将接口注入到类里面的过程。在我们......