一、什么是接口


假设我现在有3个设备,(手机、相机、笔记本电脑)

我想将"手机" 或 "相机" 里的照片传输到笔记本上,我们就必须要链接笔记本的USB口

这里的USB口就相当于一个接口,他定义了一个连接的规范


go接口


在golang中接口和USB的机制类似,我们会开放一个接口,在这个接口中会各种各样的规范

只有满足这个接口的所有的规定,才能去使用这个接口。


二、快速入门

1、定义接口

type Usb interface{  //定义接口 type [name] interface
Start()
}


如果我们想要去调用这个USB接口,就必须满足这个接口的规范

接口内定义的Start()是一个方法名,其他变量想要调用接口就必须要绑定名为Start()的方法


2、声明结构体、接口规定的方法名

type Phone struct{}   //手机
type Camera struct{} //相机


func (p Phone) Start(){fmt.Println("手机开始工作")}
func (c Camera) Start() {fmt.Println("相机开始工作")}


方法名必须和接口内定义的名称一致 


3、调用这个接口

func main(){
phone := Phone{}
camera := Camera{} //声明结构体

var usb1 Usb //声明接口变量
var usb2 Usb

usb1 = phone //将结构体变量传入到接口变量中
usb2 = camera

usb1.Start() //通过接口调用结构体定义的方法
usb2.Start()
}


当时我看到这里的时候想,这有啥用啊,和我直接"结构体.Start"有什么区别?╰(艹皿艹 )


小结


理解一下,接口本身没有直接性的功能,而是起到一个规范的作用

比如之前我们写过一些代码,我们想要调用之前的方法来实现一个功能,就需要单

独去找这些方法的位置,而接口就是将某个功能所需的所有方法放在一起,作为一个规范

其他人看到了接口内的方法名就知道要去找那些方法来实现功能了

(编程小白一枚,如果说的不对麻烦指出,感谢ヾ(≧▽≦*)o)


升级一下上面的程序,在接口添加一个新的规范stop

package main

import "fmt"

type Usb interface{
Start()
Stop() //添加一个新的规范(方法)
}



type Phone struct{}
type Camera struct{}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (c Camera) Start() {fmt.Println("相机开始工作")}

func (p Phone) Stop(){fmt.Println("手机停止工作")} //手机设备新增stop方法
func (c Camera) Stop() {fmt.Println("相机停止工作")} //相机设备新增stop方法




//上面我们一个一个调用比较麻烦,我们这里把调用操作做一个方法
type Computer struct{}

//这里将结构体变量作为参数传入usb即可
//编辑器会自动识别你传入接口变量的结构体变量下有没有接口所指定规范的方法
func (c Computer) Working(usb Usb){
usb.Start() //usb已经是接口了,会调用传入结构体变量绑定的方法
usb.Stop()
}

func main(){
Computer := Computer{}
phone := Phone{}
camera := Camera{}


//测试
Computer.Working(phone)
Computer.Working(camera)
}

返回

手机开始工作
手机停止工作
相机开始工作
相机停止工作

三、接口的注意事项

1、接口本身不能创建实例


我们不能直接拿着接口这个类型去声明一个变量,因为接口中的方法是无意义的


package main

type AIinterface interface {
Say()
}

func main(){
var a AIinterface //这里我们用接口类型去声明了一个变量
a.Say() //错误,因为接口里的方法只是一个空的模板

返回

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x1daaf6]


但是可以指向一个实现了该接口的变量


package main

import "fmt"

type AIinterface interface {
Say()
}

//我们单独去定义一个结构体,然后去实现这个方法
type Stu struct{
Name string
}
func (stu Stu) Say(){
fmt.Println("Stu Say()")
}


func main(){
var stu Stu //这个Stu结构体绑定的方法,满足AIinterface接口下所有的方法名
//也就是实现了这个方法

var a AIinterface = stu //我们是可以把这个结构体赋予到接口这个类型的
a.Say() //然后通过接口类型传入的结构体来调用方法
// (调用的是stu结构体的方法)
}

2、接口中所有方法都没有方法体,都是没有实现的方法

//正确定义
type AIinterface interface {
Say()
}



//错误定义
type AIinterface interface {
Say() {name :xxx} //接口不允许有方法体
}

3. 在golang中,一个自定义类型需要将某个接口的所有方法都实现,我们才能说这个自定义类型实现了该接口

//其实这个我们已经说了很多次了
//比如,我们下面定义了一个接口,设置了模板,实现接口需要先实现3个方法
type xxx1 interface{
1()
2()
3()
}

//我们通常会去定义一个结构体
type xxx2 struct {}

//然后给这个结构体绑定接口中要求的方法
func (x xxx2) 1(){}
func (x xxx2) 2(){}
func (x xxx2) 3(){}


//然后这个结构体实例化后就算实现了这个接口
xxx2{}

//我们就可以把这个实例化的结构体变量交给接口了
var xxx1 接口类型 = xxx2

//最后通过接口变量的名称来调用结构体的方法
xxx1.1()
xxx1.2()
xxx1.3()

4. 只要是自定义数据类型,不一定是结构体都能实现接口

package main

import "fmt"

type AIinterface interface {
Say()
}



type integer int //实现接口,不一定是结构体类型
//通过type定义的都是自定义类型,所以也可以绑定方法


func (i integer) Say(){
fmt.Println("integer Say i =",i)
}

func main(){
var i integer = 10
var b AIinterface = i
b.Say()
}

5、 一个自定义类型可以调用多个接口

package main

import "fmt"

type AIinterface interface { //定义两个接口
Say()
}
type BIinterface interface {
Hello()
}



type Monster struct{} //创建一个结构体

//让这个结构体同时实现两个接口的方法
func (m Monster) Hello(){fmt.Println("Monster hello()~")}
func (m Monster) Say(){fmt.Println("Monster Say()~")}



func main(){
var monster Monster

//使用该结构体传给两个接口变量
var a2 AIinterface = monster
var b2 BIinterface = monster

a2.Say()
b2.Hello()

//我们可以用一个自定义变量去实现多个方法
//接口调用方法时,只会调用各自接口所定义的方法,其他的方法是不会使用的

}

6、golang接口中不允许任何变量

var xx interface{
name string //这样是不可用的
}

7、继承接口


一个接口(比如A接口) 它可以继承多个别的接口(比如b、c接口)

这时如果要实现A接口,也必须将B、C接口的方法都全部实现


package main

type A interface{ //定义A接口 继承接口B、C
B //需要B、C的接口都实现才能用
C
test01()
}

type B interface{
test02()
}
type C interface{
test03()
}


//设置结构体,实现3个方法
type Stu struct{}
func (stu Stu) test01() {}
func (stu Stu) test02() {}
func (stu Stu) test03() {} //你可以试试,但凡把其他接口实现的方法关掉
//那么就会导致整个接口不可用



func main(){
var stu Stu
var a A = stu
a.test01()
a.test02()
a.test03()

}

8. interface类型默认是指针(引用类型)

9. 空接口


interface{}  空接口是一种数据类型,可以接收所有数据类型


package main

import "fmt"


type T interface{
//这里没有给任何方法,所以它是空接口
//所有类型默认实现了空接口,所以可以接收任意数据
}

type Stu struct{}

func main(){
var stu Stu
var t T = stu //赋予结构体变量
fmt.Println(t)


var x T = 11 //比如接口接收int
fmt.Println(x)


//或者 这样写interface{} 空接口,接收任何类型
var t2 interface{} = stu
fmt.Println(t2)


var num1 float64 = 8.8 //可以把任何一个类型数据给空接口
t2 = num1
fmt.Println(t2)

}

四、应用案例


这里我们直接去下面的连接中找一个用接口的包去使用一下


我们这里以Sort 包为案例

 Golang学习(二十二) 接口_自定义类型


我们向下翻一些可以看到 "type interface"的字样,表示定义的接口,点击一下


 Golang学习(二十二) 接口_开发语言_02


这是人家写好的接口,当你使用排序功能时,可以根据他接口的规范去使用


 Golang学习(二十二) 接口_自定义类型_03


可以看到这个包中定义了结构体IntSlice,绑定了接口所需的3个方法


案例 1  使用sort包的方法

package main

import (
"fmt"
"sort"
)





func main(){
//他包里面已经声明好了结构体和绑定的方法来实现接口
//我们从包里拿到结构体直接去声明,并将要排序的值放入到自定义类型中
var ss sort.IntSlice = []int{1,23,4}



var stu sort.Interface //声明sort包中的接口,将符合接口规范的结构体
stu = ss //赋予到接口中



//我们可以找到一些 他内置的函数去进行排序
//func Sort(data Interface)

sort.Sort(stu) //直接调用排序的函数,将这个接口传进去
//Sort函数中的代码就会去调用接口下的方法进行排序
fmt.Println(stu)


}

返回

[1 4 23]


如上,Sort函数中已经包含了拿到接口后,利用接口下的方法进行排序的操作


看一下Sort的函数

func Sort(data Interface) {
n := data.Len() //先拿接口下的len方法统计了下长度
quickSort(data, 0, n, maxDepth(n))
}

Golang学习(二十二) 接口_自定义类型_04


 也就验证了Sort函数的确是调用了接口下的方法去做了一些排序的操作


案例2  使用自定义的方法


我们有时候会需要根据自己的需求来进行排序,如果包里没有就需要自己写方法了


package main

import (
"fmt"
"sort"
)



type IntSlice2 []int

func (hs IntSlice2) Len() int{
return len(hs) //获取传入切片的长度
}
func (hs IntSlice2) Less(i, j int) bool{
return hs[i] < hs[j] //根据返回值去判断两个索引位的大小
}
func (hs IntSlice2) Swap(i,j int){
temp := hs[i] //当条件2满足时将两个数字替换
hs[i] = hs[j]
hs[j] = temp
}

func main(){
var ss IntSlice2 = []int{1,23,4,5,10,11,2} //声明结构体并赋值
var stu sort.Interface = ss //声明接口,将结构体赋值给接口

sort.Sort(stu) //测试 排序
fmt.Println(stu)


}

返回

[1 2 4 5 10 11 23]


可以看到,排序的返回值是正常的,而且我们也可以去修改传入的方法来实现逆序


package main

import (
"fmt"
"sort"
)



type IntSlice2 []int

func (hs IntSlice2) Len() int{
return len(hs)
}
func (hs IntSlice2) Less(i, j int) bool{
return hs[i] > hs[j] //这里,我们将小于改为大于就实现了从大到小排序
}
func (hs IntSlice2) Swap(i,j int){
temp := hs[i]
hs[i] = hs[j]
hs[j] = temp
}

func main(){
var ss IntSlice2 = []int{1,23,4,5,10,11,2}
var stu sort.Interface = ss

sort.Sort(stu)
fmt.Println(stu)


}

 返回

[23 11 10 5 4 2 1]


上面都是为了理解下接口,如果要排序数字直接用sort.ints 就可以了


案例3 结构体切片排序


我们下面创建一个结构体的切片,设置一个用户名单的表,通过年龄进行排序


package main

import (
"fmt"
"math/rand"
"sort"
)

//设置一个用户信息的结构体模板
type Hero struct{
Name string
Age int
}


//声明一个切片,切片类型是用户结构体模板,用来存放多个用户结构体的信息
type HeroSlice []Hero



//因为我们是通过年龄进行排序的,是int类型,直接把上面的方法拿过来用
func (hs HeroSlice) Len() int{
return len(hs)
}
func (hs HeroSlice) Less(i, j int) bool{
return hs[i].Age < hs[j].Age //指定对比的是年龄
}
func (hs HeroSlice) Swap(i,j int){
temp := hs[i]
hs[i] = hs[j] //这里3行替换操作,可以简写为hs[i], hs[j] = hs[j], hs[i]
hs[j] = temp
}


func main(){
var heroes HeroSlice //声明结构体切片,用来存放多个用户结构体

for i := 0; i < 10; i++{ //循环创建10个用户结构体放入到结构体切片中
hero := Hero{
Name: fmt.Sprintf("英雄%d",rand.Intn(100)),
Age: rand.Intn(100),
}
heroes = append(heroes,hero) //循环写入到切片结构体中
}


//看看排序前的顺序(此时的值是无序的)
fmt.Println("排序前--------------")
for _ , v := range heroes{
fmt.Println(v)
}

//调用结构体变量直接传进去进行排序
//这里接口会自己识别,可以不用写将结构体交给接口,上面是为了方便识别
sort.Sort(heroes)

fmt.Println("排序后--------------")
for _ , v := range heroes {
fmt.Println(v)
}

}

 返回

排序前--------------
{英雄81 87}
{英雄47 59}
{英雄81 18}
{英雄25 40}
{英雄56 0}
{英雄94 11}
{英雄62 89}
{英雄28 74}
{英雄11 45}
{英雄37 6}


排序后--------------
{英雄56 0}
{英雄37 6}
{英雄94 11}
{英雄81 18}
{英雄25 40}
{英雄11 45}
{英雄47 59}
{英雄28 74}
{英雄81 87}
{英雄62 89}

进程 已完成,退出代码为 0


可以看到,他基于我们后面的年龄对比进行了排序