10-Go语言基础之接口

Go语言非常关键的知识点之一、也是难点之一。

image-20230109143028293


雨痕Go语言笔记

接口采用了duck type方式,在程序设计中是动态类型的一种风格
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

空接口类型interface{},类似于OOP的system.Object,可以接收任意类型
。
准备交互的双方,共同遵守实现约定的规则,使得无须知道对方身份的情况下进行协作。

接口要实现的是做什么,而不关心怎么做,谁来做。

可以先实现类型,而后再抽象出所需接口。称作非侵入式设计。

概念

Go语言的主要设计者之一罗布·派克(Rob Pike)曾经说过,如果只能选择一个Go语言的特 性移植到其他语言中,他会选择接口

接口在Go语言有着至关重要的地位。如果说goroutinechannel是支撑起Go语言的并发模型 的基石,让Go语言在如今集群化多核化的时代成为一道极为亮丽的风景,那么接口是Go语言 整个类型系统的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。

Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有goroutine和channel, 而更重要的是因为Go语言的类型系统,更是因为Go语言的接口

Go语言的编程哲学因为有接口 而趋近完美。

接口特点
接口只有方法声明,没有实现,也没有数据字段。
接口可以匿名嵌入到其他接口。
对象赋值给接口时,会发生拷贝。
只有当接口存储的类型和对象都是nil时,接口等于nil。
空接口可以接收任意的数据类型。
一个类型可以实现多个接口。
接口变量名习惯以 er 结尾。

接口类型是对其它类型行为的抽象和概括,接口类型不会和特定的实现细节绑定。
Go接口独特在它是隐式实现的,这是指:一个结构体只要实现了接口要求的所有方法,我们就说这个结构体实现了该接口。

`接口`在现实世界也是有真实场景的,如同笔记本上都有USB插口,且不用担心这个插槽是为`手机`、`U盘`、`平板`哪一个准备的,因为笔记本的usb插槽和各种设备的厂家统一了USB的插槽规范。

为什么需要接口

之前学的基础数据类型字符串、整型、结构体等类型,都是明确定义好"我是谁"。

而interface接口类型更注重"我能做什么"。

接口类型就像是一种约定,概括了某一个类型应该具备哪些method。

接口实践

接口是由程序员定义的一个类型,一个接口就是一组方法的集合,接口规定了需要实现的所有方法。

接口定义语法

type 接口名 interface {
    method1(参数列表)返回值列表
    method2(参数列表)返回值列表
}

interface类型可以定义一组方法,且不需要实现这些方法!
并且interface不能有任何变量。
只要有一个变量类型,含有接口中的所有方法,就说这个变量类型实现了这个接口。

语法注意点

  • 接口类型名
    • Go语言接口命名,一般会给单词添加er,例如写方法接口名字Writer,接口名字一般需要突出该接口的含义。
    • 方法名,当接口名字首字母大写,以及方法名首字母大写,该方法可以被接口所在的包外访问。
    • 参数列表、返回值列表:变量名可以省略。

举个例子

1.定义接口

// 例如定义Writer接口,包含了Write方法
type Writer interface{
    Write([]byte) error
}

//你看到该Writer接口,或许不知道它是什么,但是可知你可以调用Write方法做点事

2.实现该接口

type Singer interface{
  //1.规范方法
  Sing()
}

// 2.实现该接口,都有谁会唱歌呢?

type Person struct{
  name string
}

// 3.给人绑定方法
func  (p *Person)Sing(){
  fmt.Println("这里的山路十八弯,这里的山路九连环!!")
}

//4. 至此就实现了接口的玩法,Person该结构体,实现了Sing方法,就实现了Singer接口,会唱歌就等于是歌手了

为什么需要接口?实战

不用接口的开发

package main

import "fmt"

// 背景,阿猫阿狗都会叫

type Cat struct{}

func (c Cat) Say() {
    fmt.Println("喵喵喵~")
}

type Dog struct{}

func (d Dog) Say() {
    fmt.Println("汪汪汪~")
}

//这时候又来了一只鸡

type Chicken struct{}

// 鸡也会叫
func (c Chicken) Say() {
    fmt.Println("咯咯哒")
}

// 1. 业务场景,什么时候动物开始叫?饿了!
// 2.鸡饿了
func ChickenHungry(c Chicken) {
    fmt.Print("饿了!!")
    c.Say()
}

// 3. 狗饿了
func DogHungry(d Dog) {
    fmt.Print("饿了!!")
    d.Say()
}

func main() {
    d1 := Dog{}
    c1 := Chicken{}
    //4.试试,让小动物叫两声!
    ChickenHungry(c1)
    DogHungry(d1)

    //5.问题来了,这家伙,要是让猫、大象、老虎、狮子,都加入这个合唱团,你得重复性写多少代码?


}

引入接口

image-20230109153014700

package main

import "fmt"

//1.接口优化
//将所有会叫的动物,统一抽象出一个类型,叫做Animaler接口

type Animaler interface {
    Say()
}

type Cat struct{}

// 3.只要会叫,实现了Say()方法,就适用于Animaler该接口
func (c Cat) Say() {
    fmt.Println("喵喵喵~")
}

//4.只要会叫,实现了Say()方法,就适用于Animaler该接口

type Dog struct{}

func (d Dog) Say() {
    fmt.Println("汪汪汪~")
}

//5.只要会叫,实现了Say()方法,就适用于Animaler该接口

type Chicken struct{}

func (c Chicken) Say() {
    fmt.Println("咯咯哒")
}

// 2. 业务场景,函数优化,接收接口类型参数
func AnimalHungry(a Animaler) {
    fmt.Print("让我看看都是饿了!!")
    a.Say()
}

func main() {
    d1 := Dog{}
    c1 := Chicken{}
    c2 := Cat{}

    //6.开始叫吧!!动物们!!
    AnimalHungry(d1)
    AnimalHungry(c1)
    AnimalHungry(c2)

    //7.可以看到,如此优雅,强大的接口开发模式,太nb了


}

1.我们使用接口类型,把所有的会叫的动物,抽象出一个Animaler类型

2.每一个具体的动物(结构体),只要实现了Say()方法,也就符合了Animaler接口要求。

电商与interface接口

image-20230109144005145

咱们都用过二维码支付,但是经常要么是打开微信、要么是打开支付宝,去扫对应的支付码,甚是麻烦;

这要是在一些客流量很大的小吃街,饭店等,支付起来慢死个人!!

是否有办法,能不那么关注到底商家提供的是哪一款二维码,以及我们到底要打开哪一个app?

聚合支付就出现了,它实现了只要提供一个通用二维码接口,不需要关心用户到底用的支付宝、还是微信去扫码,反正你扫了,就是想要付钱,该接口自动判断你的支付方式

image-20230109161525538

早期微信支付

package main

import "fmt"

// 1.早期只有微信支付
type Wechat struct{}

// 2.支付方法
func (w *Wechat) Pay(amount int64) {
    fmt.Printf("跳转微信付款中。。。已支付%.2f元\n", float64(amount))
}

//3.结账

func Checkout(obj *Wechat) {
    //支付388元
    obj.Pay(298)
}

func main() {

    //4.创建微信对象,付钱吧
    w := Wechat{}
    Checkout(&w)

}

支付宝崛起

支付宝为了干掉微信,赠送海量红包,用户量暴涨。

在生活里,我们也是自行选择,打开支付宝,还是微信,扫码发起支付动作

package main

import "fmt"

// 1.早期只有微信支付
type Wechat struct{}

// 2.支付方法
func (w *Wechat) Pay(amount int64) {
    fmt.Printf("跳转微信付款中。。。已支付%.2f元\n", float64(amount))
}

// 加入支付宝
type Zhifubao struct {
}

func (w *Zhifubao) Pay(amount int64) {
    fmt.Printf("跳转支付宝付款中。。。已支付%.2f元\n", float64(amount))
}

//3.结账

func CheckoutWechat(obj *Wechat) {
    //支付298元
    obj.Pay(298)
}

func Checkoutzfb(obj *Zhifubao) {
    //支付298元
    obj.Pay(298)
}

func main() {

    //4.创建微信对象,付钱吧
    w := Wechat{}
    CheckoutWechat(&w)

    //在生活里,我们也是自行选择,打开支付宝,还是微信,扫码发起支付动作
    z := Zhifubao{}
    Checkoutzfb(&z)

}

抽象支付接口(加入京东)

image-20230109163355928

对于商家来说,并不关心你用的哪一个支付方式,只关心Pay()方法能否正常运行。

这是一个典型的我不关心它是什么,我只关心它能做什么!

package main

import "fmt"

// 通用支付接口,规范Pay()方法
type Payer interface {
    Pay(int64)
}

type Wechat struct{}

func (w *Wechat) Pay(amount int64) {
    fmt.Printf("跳转微信付款中。。。已支付%.2f元\n", float64(amount))
}

type Zhifubao struct {
}

func (w *Zhifubao) Pay(amount int64) {
    fmt.Printf("跳转支付宝付款中。。。已支付%.2f元\n", float64(amount))
}

// 再加入京东支付
type Jd struct{}

func (j *Jd) Pay(amount int64) {
    fmt.Printf("跳转京东付款中。。。已支付%.2f元\n", float64(amount))
}

// 优化结账动作,传入通用接口类型
func Checkout(p Payer) {
    p.Pay(298)
}

func main() {
    w := Wechat{}
    Checkout(&w)
    Checkout(&Zhifubao{})
    Checkout(&Jd{})
}

小结

  • 无论是京东、支付宝、微信哪一个支付系统,我们可以把他们全部归类为支付系统,并且必须有支付方法
  • 无论是猫、狗、鸡、鸭、鹅,我们都可以把他们归类为动物系统,并且必须有 吃喝拉撒叫方法

interface类型是Go提供的一个编码工具,实际开发过程里,用不用都在你自己,从于超老师上述代码演示来看,明显的,使用接口系统后代码更加清晰易懂。

练习题,USB接口

根据interface接口语法,开发一个笔记本USB接口,并且支持华为手机、苹果手机、Ipad、接入该USB工作,提供开机关机方法。

环境要求

// 定义一个Usb接口,且定义Usb功能方法
type Usb interface {
    Poweron()
    Poweroff()
}

type Phone struct {
    Name  string
    Price int
}

type IPad struct {
    Name  string
    Price int
}

参考

package main

import "fmt"

// 定义一个Usb接口,且定义Usb功能方法
type Usb interface {
    Poweron()
    Poweroff()
}

type Phone struct {
    Name  string
    Price int
}

// 让手机Phone实现Usb接口的方法
func (p *Phone) Poweron() {
    fmt.Printf("已开机,欢迎使用%s手机、手机价格:%d\n", p.Name, p.Price)
}

// 必须实现接口所有的方法,少一个都报错  如下:Phone does not implement Usb (missing Stop method)
func (p *Phone) Poweroff() {
    fmt.Printf("关机中,感谢您使用%s手机、手机价格:%d\n", p.Name, p.Price)
}

type IPad struct{}

func (p *IPad) Poweron() {
    fmt.Println("ipad已经连接USB,开始工作")
}

func (p *IPad) Poweroff() {
    fmt.Println("ipad断开了USB,停止工作")
}

// 电脑都提供了USB标准接口
type Computer struct {
    Name string
}

func (c *Computer) Start(u Usb) {
    fmt.Printf("----欢迎使用%s笔记本---\n", c.Name)
    u.Poweron()
}

func (c *Computer) Stop(u Usb) {
    fmt.Printf("----欢迎使用%s笔记本---\n", c.Name)
    u.Poweroff()
}

func main() {
    //分别创建结构体对象
    c := Computer{"神州笔记本"}
    apple := Phone{" iphone 14 pro max", 9999}
    huawei := Phone{"huawei mate 50 pro", 7999}
    i := IPad{}

    //开机
    c.Start(&apple)
    c.Start(&huawei)
    c.Start(&i)
    fmt.Println()
    //关机
    c.Stop(&apple)
    c.Stop(&huawei)
    c.Stop(&i)
}

接口类型变量

package main

import "fmt"

// 通用支付接口,规范Pay()方法
type Payer interface {
    Pay(int64)
}

type Wechat struct{}

func (w *Wechat) Pay(amount int64) {
    fmt.Printf("跳转微信付款中。。。已支付%.2f元\n", float64(amount))
}

type Zhifubao struct {
}

func (w *Zhifubao) Pay(amount int64) {
    fmt.Printf("跳转支付宝付款中。。。已支付%.2f元\n", float64(amount))
}

// 再加入京东支付
type Jd struct{}

func (j *Jd) Pay(amount int64) {
    fmt.Printf("跳转京东付款中。。。已支付%.2f元\n", float64(amount))
}

// 优化结账动作,传入通用接口类型
func Checkout(p Payer) {
    p.Pay(298)
}

func main() {
    //实现了Payer接口的几个结构体如下,都实现了Pay()方法
    w := Wechat{}
    j := Jd{}
    z := Zhifubao{}

    //重点:一个接口类型的变量,可以接收所有实现了该接口的类型变量
    var p Payer
    p = &w
    p.Pay(398) //微信支付

    p = &j
    p.Pay(668)

    p = &z
    p.Pay(1500)

}

值接受者、指针接受者、接口关系

image-20230109171657713

1. 值类型接受者,实现了xx接口之后,无论结构体变量是值、还是指针,都可以赋值给接口变量
2. 指针类型接受者,实现了xx接口之后,必须传入结构体指针,才可以给改接口变量赋值
package main

import "fmt"

// 定义一个Usb接口,且定义Usb功能方法
type Usb interface {
    Poweron()
    Poweroff()
}

type Phone struct {
    Name  string
    Price int
}

// 让手机Phone实现Usb接口的方法
func (p *Phone) Poweron() {
    fmt.Printf("已开机,欢迎使用%s手机、手机价格:%d\n", p.Name, p.Price)
}

// 必须实现接口所有的方法,少一个都报错  如下:Phone does not implement Usb (missing Stop method)
func (p *Phone) Poweroff() {
    fmt.Printf("关机中,感谢您使用%s手机、手机价格:%d\n", p.Name, p.Price)
}

// Ipad结构体
type IPad struct{}

// 方法接收的是值类型接受者
func (p IPad) Poweron() {
    fmt.Println("ipad已经连接USB,开始工作")
}

func (p IPad) Poweroff() {
    fmt.Println("ipad断开了USB,停止工作")
}

// 电脑都提供了USB标准接口
type Computer struct {
    Name string
}

func (c *Computer) Start(u Usb) {
    fmt.Printf("----欢迎使用%s笔记本---\n", c.Name)
    u.Poweron()
}

func (c *Computer) Stop(u Usb) {
    fmt.Printf("----欢迎使用%s笔记本---\n", c.Name)
    u.Poweroff()
}

func main() {
    //分别创建结构体对象
    c := Computer{"神州笔记本"}
    apple := Phone{" iphone 14 pro max", 9999}
    i := IPad{}

    //开机
    c.Start(&i)
    c.Start(i)

    //c.Start(apple)
    /*
    ./m1.go:63:10: cannot use apple (variable of type Phone) as type Usb in argument to c.Start:
        Phone does not implement Usb (Poweroff method has pointer receiver)
    告诉你了,apple对象的方法,poweroff,是一个指针接收器,你得传入指针
    */

}

类型与接口(深入学习)

一个类型可以实现多个接口

  • 一个类型可以同时实现多个接口,但接口是相互独立的。

  • 例如男人,可以抽象出如下多个通用接口,如下接口相互独立。

    • 上班
    • 打游戏
    • 谈对象
    • 明显的不止男人,女人也能实现这些接口。
package main

import "fmt"

// 抽象俩接口,定义方法
type Worker interface {
    Work()
}

type Player interface {
    Play()
}

// 1.女娲捏泥人了
type Person struct {
    Name string
    Age  int8
}

// 2.人可以有技能
func (p *Person) Work() {
    fmt.Printf("%s一天工作10个小时,月薪3500!我TMD!!今年他%d岁\n", p.Name, p.Age)
}

func (p *Person) Play() {
    fmt.Printf("%s已经打了%d年游戏!!他已快活似神仙!!\n", p.Name, p.Age)
}

func main() {

    //3.女娲造人了
    jack := Person{"jack", 29}

    //4.实现了接口方法的结构体,可以赋值操作
    //可见,
    var w Worker = &jack
    var p Player = &jack
    w.Work()
    p.Play()

}

多个类型实现同一接口

在我们现实世界里,会的除了有人类,还有动物。

用接口演示这个关系。

package main

import "fmt"

// 大千世界,小小的人类,除了上班,还得要学会玩啊!
// 这个玩的借口,不止人,动物也会玩。。。
type Player interface {
    Play()
}

// 1. 女娲捏泥人了!!
type Person struct {
    Name string
    Age  int
}

func (p *Person) Play() {
    fmt.Printf("%s会玩,抽烟喝酒烫头都会玩!今年%d岁!!\n", p.Name, p.Age)
}

// 2.女娲捏动物了!!
type Animal struct {
    Name string
    Age  int
}

func (a *Animal) Play() {
    fmt.Printf("%s也会玩,爱玩泥巴!!今年%d岁!!\n", a.Name, a.Age)
}

func main() {

    //3.女娲造人了
    jack := Person{"jack", 29}

    //4.女娲造了一个哈士奇
    dog := Animal{"哈士奇", 5}

    //5.接口变量赋值
    var p Player
    p = &jack
    p.Play()
    //6.多个类型可以实现同一个接口
    p = &dog
    p.Play()

}

结构体嵌套与接口实现

一个接口的所有方法,可以由多个类型共同实现,接口的方法可以由通过结构体嵌套来实现。

Go代码举个栗子

现实社会里,父亲努力工作,儿子就是吃喝玩乐。

一个梦想的生活就是工作、玩、睡觉

package main

import "fmt"

// 于超老师梦想就是成天玩、吃、睡
type Dreamer interface {
    Work()
    Play()
    Sleep()
}

// 1. 女娲造人了!!父亲来了
type Father struct {
    Name string
    Age  int
}

// 父亲努力工作养
func (f *Father) Work() {
    fmt.Printf("%s作为一名父亲,努力工作,今年%d岁。。\n", f.Name, f.Age)
}

// 2.女娲造人了!!儿子来了
type Son struct {
    Name   string
    Age    int
    Father //儿子长大后也学会了工作!!儿子替父亲实现了梦想!!继承了work的能力
}

// 3.儿子负责玩、睡觉
func (s *Son) Play() {
    fmt.Printf("%s负责玩,快乐成长,今年%d岁。。\n", s.Name, s.Age)
}

func (s *Son) Sleep() {
    fmt.Printf("%s睡着睡着就长大了。。", s.Name)
}

func main() {

    //4.女娲捏人了!!
    s1 := Son{
        "张小狗",
        18,
        Father{
            "老张头",
            55,
        },
    }

    //5.接口变量赋值
    var d1 Dreamer = &s1
    d1.Play()
    d1.Work()
    d1.Sleep()
}

/*
张小狗负责玩,快乐成长,今年18岁。。
老张头作为一名父亲,努力工作,今年55岁。。
张小狗睡着睡着就长大了。。

*/

接口组合

  • 接口与接口之间支持互相嵌套,称为新的接口类型;
  • Go的标准库有很多此类写法参考。

// Implementations must not retain p.
type Reader interface {
    Read(p []byte) (n int, err error)
}


type Writer interface {
    Write(p []byte) (n int, err error)
}


type Closer interface {
    Close() error
}

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

// ReadWriter 是对基本 Read 和 Write 方法进行分组的接口。
type ReadWriter interface {
    Reader
    Writer
}

// ReadCloser 是对基本的 Read 和 Close 方法进行分组的接口。
type ReadCloser interface {
    Reader
    Closer
}

//WriteCloser 是对基本 Write 和 Close 方法进行分组的接口。
type WriteCloser interface {
    Writer
    Closer
}
  • 多个接口类型,可以组合为新接口类型,如此看出了用法。

  • 接口嵌套接口,即可创造出新的接口。

package main

import "fmt"

// 1.上班族
type Worker interface {
    work() //成天就知道上班

}

// 2.玩乐族
type Player interface {
    play() //成天就知道玩
}

// 3.普通人接口,会上班,会玩就是个普通人呗
/*
type Peopler interface {
    work()
    play()
}

*/

// 4.接口支持组合嵌套
type Peopler interface {
    Worker
    Player
}

//5.实现Peopler接口,实现组合的其他接口的方法

type People struct {
    Name string
    Age  int
}

func (p *People) work() {
    fmt.Printf("%s 开始美好的工作一天!!\n", p.Name)
}

func (p *People) play() {
    fmt.Printf("%s 玩的那叫一个嗨!!\n", p.Name)

}

func main() {
    // 6.女娲造人了
    jack := People{"jack", 18}
    // 7.接口变量赋值
    var p Peopler

    p = &jack
    p.work()
    p.play()

}

利用接口组合特点,可以构造出新的接口;

只要实现该组合接口中,所有的方法,就实现了该接口。

空接口

空接口是指空的、没有任何方法的interface。

这就很神奇了,接口语义规范是,实现了所有该接口的方法,就实现了该接口。

那么空接口,不就可以代表任意数据类型了吗?

因此空接口类型的变量,可以接收任意其他类型的变量!!

package main

import "fmt"

//1.空接口,没有任何方法

type Anyer interface {
}

// 2. 随便定义任意数据类型
var name string

type People struct {
    Name string
    Age  int
}

var Score float32

func main() {
    //3.空接口变量,可以赋值给任意的数据类型
    var a Anyer

    //4.创建其他任意类型变量
    name = "三胖"
    a = name
    fmt.Printf("类型:%T、值:%#v\n", a, a)

    a = true
    fmt.Printf("类型:%T、值:%#v\n", a, a)

    a = 3.1415926
    fmt.Printf("类型:%T、值:%#v\n", a, a)

    a = People{"张飞", 18}
    //注意空接口是没有任何属性的,不允许去调用如索引,或者字段值等
    fmt.Printf("类型:%T、值:%#v\n", a, a)

}

空接口在函数中应用

image-20230110183851541

空接口非常给力,标准库里有大量应用。

特别是在函数传参中,我们时长要去考虑,函数参数类型如何设置。

使用空接口作为函数的参数,即可实现接收任意类型的函数参数!!

package main

import "fmt"

// 1.普通函数
func SayHi(name string) {
    fmt.Printf("你好呀,%s、参数name类型:%T\n", name, name)
}

// 2.空接口传参
func SayAny(a interface{}) {
    fmt.Printf("参数值:%v \t 参数n类型:%T\n", a, a)

}

type Student struct {
    Name, Addr string
}

func main() {
    //3.函数调用
    SayHi("老张头")

    //4.空接口参数
    SayAny(123)
    SayAny("接口给力呀!!")
    SayAny('6')
    SayAny([...]int{6, 6, 6})
    SayAny([]string{"狗蛋", "毛蛋", "傻蛋"})
    SayAny(Student{"于超老师", "江苏淮安"})
    // map
    stuInfo := map[string]string{
        "name": "小于",
        "addr": "北京",
    }
    SayAny(stuInfo)
}

接口值

image-20230111104846153

从上述代码来看,接口类型变量的值,可以是任意一个实现了该接口的类型的值

空接口就是一个特殊的存在,所有数据类型都赋值给空接口变量。

那么接口值除了要记录具体的值以外,还要记录这个值所属的类型

因此接口变量的值由两部分组成 类型组成,这两部分并且还会根据传入的值不同而变化,因此也有两个名字。

  • 动态类型
  • 动态值

image-20230111105014610

接口动态类型、动态值

image-20230111105638518

package main

import "fmt"

// 1.Dreamer
type Dreamer interface {
    dream()
}

// 2. 谁都会做梦,人类
type People struct {
    Name string
}

func (p *People) dream() {
    fmt.Println("做个好梦吧,少年")
}

// 3.猫猫
type Cat struct {
    Name string
}

func (c *Cat) dream() {
    fmt.Println("猫猫成天都在睡觉。")
}

func main() {
    //4.接口变量,初始值
    var d Dreamer
    if d == nil {
        fmt.Printf("接口值:%#v、类型:%T\n", d, d) //接口值:<nil>、类型:<nil>
    }
    //5.空接口无法调用方法
    //d.dream()  panic: runtime error: invalid memory address or nil pointer dereference

    //6.给接口赋值
    d = &People{"于超"}
    d.dream()
    if d != nil {
        fmt.Printf("接口值:%#v、类型:%T\n", d, d) //接口值:<nil>、类型:<nil>
    }
}

此时我们是将*People结构体指针赋值给了变量d。

接口d变量动态类型就是*People、动态值就是 People{Name:"于超"}

image-20230111112612089

再次修改动态值

package main

import "fmt"

// 1.Dreamer
type Dreamer interface {
    dream()
}

// 2. 谁都会做梦,人类
type People struct {
    Name string
}

func (p *People) dream() {
    fmt.Println("做个好梦吧,少年")
}

// 3.猫猫
type Cat struct {
    Name string
}

func (c *Cat) dream() {
    fmt.Println("猫猫成天都在睡觉。")
}

func main() {
    //4.接口变量,初始值
    var d Dreamer
    if d == nil {
        fmt.Printf("接口值:%#v、类型:%T\n", d, d) //接口值:<nil>、类型:<nil>
    }
    //5.空接口无法调用方法
    //d.dream()  panic: runtime error: invalid memory address or nil pointer dereference

    //6.给接口赋值
    d = &People{"于超"}
    d.dream()
    if d != nil {
        fmt.Printf("接口值:%v、类型:%T\n", d, d) // 接口值:&main.People{Name:"于超"}、类型:*main.People
    }
    fmt.Println()
    //7.修改接口动态值
    c := &Cat{"大橘猫"}
    d = c
    d.dream()
    if d != nil {
        fmt.Printf("接口值:%v、类型:%T\n", d, d) // 接口值:&{大橘猫}、类型:*main.Cat
    }
}

/*
接口值:<nil>、类型:<nil>
做个好梦吧,少年
接口值:&{于超}、类型:*main.People

猫猫成天都在睡觉。
接口值:&{大橘猫}、类型:*main.Cat

*/

接口类型断言

既然接口可以赋值任意类型,且有动态类型、动态值。

有办法动态判断,接口值的类型吗?

前面已经看到,通过fmt.Printf()可以打印出接口值类型。
原理是通过反射机制,在程序运行时获取到动态类型的名称。

image-20230111115000150

Go语言还支持类型断言的语法从接口值中获取实际值的动态类型

x.(T)  
x表示接口变量
T表示断言x可能得类型。

类型断言后返回2个值
1. x转化为T类型的变量
2. 布尔值

案例

package main

import "fmt"

func main() {
    var i interface{}

    i = "我当时大意了!"

    //v, ok := i.([]string)
    v, ok := i.(string)
    if ok {
        fmt.Printf("%#v 类型断言成功,类型:%T\n", v, v) // "我当时大意了!" 类型断言成功,类型:string

    } else {
        fmt.Printf("%#v 类型断言失败 类型:%T\n", v, v)
    }
}

Switch case类型断言

若是需要对接口值,进行多种类型断言,推荐做法是switch语句。

package main

import "fmt"

//1.函数传入空接口,进行类型断言

func AnyType(i interface{}) {
    // switch case语句多次判断v到底是什么类型,固定写法,根据case后的值去判断类型
    switch v := i.(type) {
    case string:
        fmt.Printf("%v is string..\n", v)
    case int:
        fmt.Printf("%v is int..\n", v)
    case bool:
        fmt.Printf("%v is bool..\n", v)
    case []string:
        fmt.Printf("%v is slice..\n", v)
    default:
        fmt.Printf("%v 暂未支持该类型传入!!\n", v)
    }
}

func main() {
    //2.类型测试
    AnyType("hello")
    AnyType(666)
    AnyType(false)
    AnyType([]string{"老李", "王老六", "张老头"})
    AnyType(map[string]int{"年龄": 18})

}

/*
hello is string..
666 is int..
false is bool..
[老李 王老六 张老头] is slice..
map[年龄:18] 暂未支持该类型传入!!

*/

接口怎么用?

由于接口类型变量可以存储不同类型的值,特别是空接口interface{},灵活性很强,但也不该为了偷懒而去滥用。

建议是只有两个及以上的类型,必须以相同的方式处理时再去抽象接口,否则导致程序不需要的运行损耗,以及阅读困难。

Go语言的接口用于实现代码的抽象、解耦,也可以隐藏内部代码的实现,缺点是增加代码阅读难度。

Go异常处理与接口

Go语言把程序错误当做一个特殊的值处理,不同于如Python的try、except捕获异常。

image-20230111152550370

error接口只有一个Error方法且返回描述错误信息的字符串。

当Go函数或接口的返回值需要携带错误处理,一般是将error当做最后一个参数。

例如标准库os.Open()函数

image-20230111152849939

因为error是接口类型,默认值是nil类型,因此实际开发里,会经常让函数的返回值和nil判断,是否程序出错了,说道这里就可以理解Go的异常处理机制了吧。

f, err := os.Open("./hello.go")
if err != nil {
    fmt.Println("Error,错误信息:", err)
    return
}

创建错误信息

image-20230111154527947

我们可以通过标准库的error包,主动创建错误信息

package main

import (
    "errors"
    "fmt"
)

// 函数主动返回错误
func myError(a, b int) (int, error) {
    //例如 被除数不能是0
    if b == 0 {
        return 1, errors.New("错误!被除数不能是0!!")
    }
    return a + b, nil
}

func main() {
    res, err := myError(5, 7)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("总和", res)

}
Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2023-01-11 15:55:49

results matching ""

    No results matching ""