Go 语言接口语法指南 | 进阶接口语法学习

silverwq
2024-01-03 / 0 评论 / 70 阅读 / 正在检测是否收录...

概述

接口主要是用于实现多态的效果,接口是一个类型,一个抽象的类型,既然是类型,就可以定义接口类型的变量。

定义

接口好像用于定义方法的,写上方法签名列表即可

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1 // 签名
    方法名2( 参数列表2 ) 返回值列表2
    …
}

接口名称:我们一般会在接口名称后面添加 er

方法签名:

  1. 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问,也就是说可以被其它包所实现;
  2. 参数列表和返回值列表中的参数 变量名可以省略
type sayer interface {
  Say() string
}

结构体变量赋值

如果一个变量定义为接口类型的变量,那么如果一个结构体实现了这个接口,并且方法的接收者都是值类型的,那么这个结构体的值类型和指针类型都可以赋予给这个接口变量。

例如下例中,结构体 dog,实现了 Mover 接口的 move 和 eat 方法,并且这个两个方法的接受者都是值类型,所以 dog{} 可以赋值给这个 Mover 接口变量,&dog{} 也可以赋值给这个 Mover 接口变量

package main

import "fmt"

type Mover interface {
    move()
    eat()
}

// dog 类型的方法的接受者都是值类型
type dog struct{}

func (d dog) move() {
    fmt.Println("狗会动")
}

func (d dog) eat() {
    fmt.Println("吃")
}

func main() {
    var x Mover
    x = &dog{} // 指针类型可以赋值
    x.move()

    var y Mover
    y = dog{} // 值类型可以赋值
    y.move()
}

但是,如果结构体方法的接收者存在指针类型,那么只能这个结构体的指针类型都可以赋予给这个接口变量,值类型不行,会报错。

如下所示 move 方法是指针类型,所以只能 &dog{} 赋予给 Mover 接口变量,用 dog{} 值类型赋予接口变量的话,将会报错

package main

import "fmt"

type Mover interface {
    move()
    eat()
}

type dog struct{}

// move 方法的接受者是指针类型*dog
func (d *dog) move() {
    fmt.Println("狗会动")
}

func (d dog) eat() {
    fmt.Println("吃")
}

func main() {
    var x Mover
    x = &dog{} // 指针类型可以赋值
    x.move()

    var y Mover
    y = dog{} // 值类型不可以赋值了,会报错!!!
    y.move()
}

空接口

接口是行为规范的集合,空接口没有任务行为规范,也就是接口方法为空。因为没有任何行为规范,所以任何类型都可以属于空接口类型。所以空接口可以接收任何参数。

package main

import "fmt"

// Mover 自定义空接口
type Mover interface {
}

// dog 空接口参数arg
func dog(arg Mover) {

}

func main() {
    // 空接口可以接受任何参数
    dog(10)
    dog("10")

    // 空接口可以接受任何参数
    var a Mover
    a = 10
    a = "10"

    // Println 参数类型就是空接口
    fmt.Println(a)
}

go 里其实已经自带了一个空接口类型 interface{},不需要我们自定义(上例的 Mover 空接口)

package main

import "fmt"

func main() {
    // go里自带的空接口类型interface{}
    var a interface{}
    a = 10
    a = "10"

    // Println 参数类型就是空接口
    fmt.Println(a)
}

go1.18 之后,官方更加建议使用 any 来替代 interface{},这样可以少写几个字母,没有别的差别。

package main

import "fmt"

func main() {
    // go1.18之后,官方推荐用any替代interface{}
    var a any
    a = 10
    a = "10"

    // Println 参数类型就是空接口
    fmt.Println(a)
}

断言

由于空接口可以存任何类型的值,有时候我们想要知道,空接口类型的值到底是什么类型,这个时候就需要对值进行断言。

package main

import "fmt"

func main() {
    // 定义一个空接口x
    var x interface{}
    x = "Hello 沙河"
    v, ok := x.(string) // 断言是否是字符串,ok为true或false,v为变量的值
    fmt.Println(v, ok)
}

断言配合switch使用

anyVal.(type)的形式必须配合switch使用

var anyVal interface{}
anyVal = "123"
switch specificTypeVal := anyVal.(type) {
case string:
  fmt.Println(specificTypeVal) // 输出字符串3
}

有时候我们希望结构体必须实现某个接口,如果没有实现的话,就编译报错,启动不起来,可以这样写

package main

import (
    "io"
)

type MyStruct struct{}

func (m *MyStruct) Read(p []byte) (n int, err error) {
    return 0, nil
}

// Impl 或者写个方法,返回这个接口类型也可以
func (m *MyStruct) Impl() io.Reader {  
   return (*MyStruct)(nil)  
}

// 这个两种方式,方法的接受者可以是指针,也可以是值类型
var _ io.Reader = (*MyStruct)(nil) // 把nil转成*MyStruct类型
var _ io.Reader = &MyStruct{}

type MyStructCaseTwo struct{}

func (m MyStructCaseTwo) Read(p []byte) (n int, err error) {
    return 0, nil
}

// 方法的接受这必须是指针类型
var _ io.Reader = MyStructCaseTwo{}

func main() {

}

接口的嵌套

接口与接口间可以通过嵌套创造出新的接口,嵌套得到的接口的使用与普通接口一样

// Sayer 接口
type Sayer interface {
    say()
}
// Mover 接口
type Mover interface {
    move()
}
// 接口嵌套
type animal interface {
    Sayer
    Mover
}

接口的实现

接口是隐式实现的,一个对象只要全部实现了接口中的方法,那么就实现了这个接口;换句话说,接口就是一个 需要实现的方法列表

type sayer interface {
  Say() string
}
type Cat struct{}

// 实现了sayer接口
func (c Cat) Say() string {
  return "喵喵喵"
}

一个类型可以实现多个接口,只需要实现每个接口里的方法即可

type Sayer interface {
   say()
}
type Mover interface {
    move()
}
type dog struct {
    name string
}

func (d dog) say() {
    fmt.Printf("%s会叫汪汪汪\n", d.name)
}

func (d dog) move() {
    fmt.Printf("%s会动\n", d.name)
}

func main() {
   var x Sayer
 var y Mover

 a := dog{name: "旺财"}
  x = a
   y = a
   x.say()
 y.move()
}

还可以多个类型实现同一个接口,这就是多态的实现,同一个接口类型,不同对象有不同的表现,如下所示,同一个 move 方法,dog 结构体实例和 car 结构体实例,执行的结果是不一样的

type Mover interface {
  move()
}
type dog struct {
    name string
}
type car struct {
   brand string
}

func (d dog) move() {
  fmt.Printf("%s会跑\n", d.name)
}
func (c car) move() {
   fmt.Printf("%s速度70迈\n", c.brand)
}

func main() {
   var x Mover
   x = dog{name: "旺财"}
   x.move()
   x = car{brand: "保时捷"}
   x.move()
}
0

评论 (0)

取消