12.Go语言干货-接口类型

1.前言

永远不要高估自己
接口(interface)是一种类型!!

2.接口类型

1.接口是一种类型,使用关键字typeinterface进行定义
2.接口类型只定义方法,不关心数据
3.通俗地讲接口类型定义了规则,不关心规则的实现

3.接口类型的定义

接口的定义格式

type 接口类型名 interface {
	方法名1(参数列表1) 返回值列表1
	方法名2(参数列表2) 返回值列表2
	...
}
  • 接口类型名:从type关键字可以看出接口就是类型的一种,接口类型名称就是给接口去哟个名字。接口类型命名规则一般会在单词的后面添加 er,比如说写操作的接口名称名义为 Writer,字符串接口叫做 Stringer,接口类型的名称最好能体现接口的含义!
  • 方法名:当方法名称首字母大写并且接口类型名称首字母也是大写,这个方法可以被接口所在包之外的代码使用
  • 参数列表、返回值列表:可以省略

举个栗子:

type Writer interface(
	Write([]byte) error
)

所见即所得,看见接口类型名称与方法名称大概能猜出,这个接口的功能!

4.实现接口的条件

接口类型就是一个需要实现的方法列表,一个结构体示例实现了接口中的所有方法,那么就实现了这个接口类型

1.我们定义一个Caller接口:

// Caller 接口类型
type Caller interface {
	call()
}

2.定义DogCat 两个结构体:

type Dog struct{}

type Cat struct{}

3.因为Caller接口里面有一个call方法,所以我们需要给DogCat两个结构体分别实现Call方法

// Dog 实现了Caller接口类型
func (d Dog) call(){
	fmt.Println("汪汪汪...")
}
// Cat 实现了Caller接口类型
func (c Cat) call(){
	fmt.Println("喵喵喵...")
}

接口的实现即时这么简单,只要实现了接口中的所有方法,就实现了这个接口

5.接口类型变量

接口类型变量能够存储所有实现了该接口的实例。
列如上面的例子中,DogCat的实例实现了,接口Caller中的call方法,那么定义一个Caller类型的变量就可以存储Dog与Cat的实例。

func main(){
	var x Caller // 声明一个Caller类型的变量x
	c := Cat{} // 实例化Cat
	d := Dog{} // 实例化Dog
	x = c // 可以吧cat的实例赋值给x
	x.call() // 喵喵喵
	x = d // 可以吧dog的实例赋值给x
	x.call() // 汪汪汪
}

观察下列代码,感受_的妙用

// 摘自gin框架routergroup.go
type IRouter interface{ ... }

type RouterGroup struct { ... }

var _ IRouter = &RouterGroup{}  // 确保RouterGroup实现了接口IRouter

6.值接受者与指针接受者实现接口的区别

定义一个Mover接口类型与一个dog结构体

type Mover interface {
	move()
}

type cat struct{}

6.1 值类型接受者实现接口

func (c cat) move(){
	fmt.Println("猫动起来了")
}

此时实现接口类型的是cat类型

func main(){
	var x Mover
	var Tom = cat{} //Tom是cat类型
	x = Tom // x可以接受cat类型
	var jialaolian = &cat{} // jiaolaolian是*cat类型
	x = jialaolian // x 可以接受*cat类型
	x.move()
}

从代码可以看出,使用值接受者实现接口之后,不管是cat结构体还是结构体指针*cat类型的变量都可以赋值给该接口变量

6.2 指针接受者实现接口

同样的代码我们再来测试一下使用指针接受者有什么区别

func (c *cat) move(){
	fmt.Println("猫动起来了")
}

func main(){
	var x Mover
	var Tom = cat{} // Tom是cat类型
	x = Tom // x接收cat类型报错
	var jialaolian = &cat{}
	x = jialaolian // x可以接受*cat类型
}

此时实现Mover接口类型是*cat类型,所以不能给x传入cat类型的Tom,此时x只能传入*cat类型的值

7.结构体类型与接口类型的关系

7.1 一个结构体类型实现多个接口类型

一个结构体类型可以同时实现多个接口类型,并且接口类型之间互相独立。

举个栗子:猫可以叫,也可以动,我们可以分别定义CallerMover接口

type Caller interface{
	call()
}

type Mover interface {
	move()
}

cat 即实现了Caller接口,也实现了Mover接口

type cat struct {
	name string
}

func (c cat) call() {
	fmt.Println("喵喵喵")
}

func (c cat)move() {
	fmt.println("动起来")
}

func main(){
	var x Caller
	var y Mover
	var c1 = cat(name:Tom)
	x = c1
	y = c1
	x.call()
	x.move()
}

7.2 多个结构体类型实现同一接口类型

。。。

8 接口类型嵌套

接口与接口之前可以通过嵌套创造出新的接口

// Caller 接口
type Caller interface {
	call()
}

// Mover 接口
type Mover interface {
	move()
}

//接口嵌套
type Animal interface {
	Caller
	Mover
}

嵌套得到的接口使用与普通接口一样

type cat struct {
	name string
}

func (c cat) call(){
	fmt.Println("喵喵喵")
}

func (c cat)move(){
	fmt.Println("动起来")
}
func main(){
	var x Animal
	x = cat{name:"Tom"}
	x.move()
	x.call()
}

9 空接口

9.1 空接口的定义

空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口

空接口的变量,可以存储任意类型的变量

package main

import "fmt"

func main() {
	var x interface{}
	s := "你好啊"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}

`
type:string value:你好啊
type:int value:100
type:bool value:true`
`

9.2 空接口的应用

9.2.1 空接口作为函数的参数

使用空接口类型可以接受任意类型的参数

// 空接口作为函数形参
func show(a interface{}){
	fmt.Printf("type:%T value:%v",a,a)
}

9.2.2 空接口作为map的值

使用空接口,实现可以保存任意值得字典

// 空接口作为map的值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "sss"
studentInfo["age"] = 18
studentInfo["gender"] = true

fmt.Println(studentInfo)

10 类型断言

空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?

10.1 接口值

一个接口的值(简称接口值)是由一个具体类型具体类型的值 两部分组成。
这两部分分别称之为接口的动态类型动态值

在这里插入图片描述

要想判断空接口中的值,这个时候可以使用类型断言。

类型断言格式

x.(T)
  • x:表示类型为interface{}的变量
  • T:表示x可能的类型

举个栗子:

func main() {
	var x interface{}
	x = "Hello 沙河"
	v, ok := x.(string)
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("类型断言失败")
	}
}

上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:

func justifyType(x interface{}) {
	switch v := x.(type) {
	case string:
		fmt.Printf("x is a string,value is %v\n", v)
	case int:
		fmt.Printf("x is a int is %v\n", v)
	case bool:
		fmt.Printf("x is a bool is %v\n", v)
	default:
		fmt.Println("unsupport type!")
	}
}

因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。

关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

相关文章

Golang的文档和社区资源:为什么它可以帮助开发人员快速上手...
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有...