Go 1.19.4 接口-Day 10

1. 接口

1.1 基本介绍

接口中到底应该定义些什么?
在Go语言中,接口是声明函数的集合,但只有函数签名,没有具体的功能。
属于是面向对象中,行为的约束,面向对象中的类有自己的属性(可以当成数据成员或字段)、方法(可以当成函数,函数有自己的操作或行为),如下代码:

type 接口名称 interface {
	dog() // 方法1签名
	cat() // 方法2签名
	方法1 (参数列表1) 返回值列表1
    方法2 (参数列表2) 返回值列表2
}

上面这个接口A,要求实现者,必须实现方法1和方法2(就是命名函数时,只能用接口中的函数名命名)。
也就是说,如果有谁声称实现了某某接口,那就必须实现该接口中的所有方法。

1.2 使用接口注意事项

  • 接口命名习惯在接口名后面加上er后缀。
  • 参数列表、返回值列表参数名可以不写。
  • 如果要在包外使用接口,接口名应该首字母大写,方法要在包外使用,方法名首字母也要大写。
  • 接口中的方法应该设计合理,不要太多,甚至越少越好。

2. 接口实现

如果一个结构体实现了一个接口声明的所有方法,就说结构体实现了该接口。
一个结构体可以实现多个接口。

2.1 实现单个接口

package main

import "fmt"

// 定义一个"运动"接口
type Sport interface {
	// Go倾向小接口,只有一个方法都行
	run()  // 跑步
	jump() // 跳跃
}

// 该接口由"人"来实现
type Person struct {
	name string
	age  int
}

// 实现接口
///实现接口方法1
func (*Person) run() {
	fmt.Println("跑步")
}

///实现接口方法2
func (*Person) jump() {
	fmt.Println("跳跃")
}

func main() {
	p1 := new(Person) // 创建一个新的指针实例,减少数据拷贝次数
	p1.jump()
	p1.run()
}
==============调试结果==============
跳跃
跑步

再来看一个特殊示例

package main

import "fmt"

type Sport interface {
	run()  // 跑步
	jump() // 跳跃
}

type Person struct {
	name string
	age  int
}

func (*Person) run() {
	fmt.Println("跑步")
}

func (*Person) jump() {
	fmt.Println("跳跃")
}

// 我这里新加了一个成员方法,请问Person结构体实现了Sport接口吗?
// 当然实现了,上面两个成员方法已经实现了Sport接口,我当然可以继续定义Person结构体的其他方法。
func (t *Person) swim() {
	fmt.Println("游泳")
}

func main() {
	p1 := new(Person)
	p1.jump()
	p1.run()
	p1.swim()
}
==============调试结果==============
跳跃
跑步
游泳

2.1.1 把接口类型赋值给变量

package main

import "fmt"


type Sport interface {
	run()
	jump()
}

type Person struct {
	name string
	age  int
}

func (*Person) run() {
	fmt.Println("跑步")
}

func (*Person) jump() {
	fmt.Println("跳跃")
}

func (t *Person) swim() {
	fmt.Println("游泳")
}

func main() {
	p1 := new(Person)
	p1.jump()
	p1.run()
	p1.swim()
	fmt.Println("-------------------")
	var a Sport = p1 // 把接口类型赋值给变量,该变量就是个接口类型的变量了
	fmt.Println(a)
	a.jump() // 可以直接调用接口中的方法
	a.run()
}
==============调试结果==============
跳跃
跑步
游泳
-------------------
&{ 0}
跳跃
跑步

3. 接口嵌入(组合)

除了结构体可以嵌套,接口也可以。接口嵌套组合成了新接口。

type Reader interface {
 Read(p []byte) (n int, err error)
}
type Closer interface {
 Close() error
}
type ReadCloser interface {
 Reader
 Closer
}

这样写的好处,就是如果需要同时调用Reader和Closer,直接调用ReadCloser就行了。

4. 空接口

4.1 什么是空接口

在 Go 语言中,空接口(interface{})是一个没有方法的接口。这意味着任何类型都可以实现空接口(或者说空接口可以匹配任意类型的数据),因为它们不需要满足任何方法的要求。
空接口可以存储任何类型的值,这使得它在需要存储未知类型数据时非常有用。

4.2 定义空接口

4.2.1 基本定义

func main() {
	var b interface{} // 空接口类型,注意是空接口类型,这部分interface{}表示数据类型
	var c any // 这样也行,any是interface{}的别名(鼠标移到any上能看到)
}

4.2.2 空接口和任意数据类型组合

func main() {
	var a = 500
	var b interface{} // 空接口,b的类型是接口类型,但是个空接口
	// var b any // 或者这样声明,更好理解,any表示任意数据类型

	// 为什么b能等于a?因为interface{}是空接口,里面没有任何方法。
	// 换句话说,不管什么类型,都能实现空接口。
	b = a
	fmt.Printf("b的类型:%T\nb的值:%+[1]v\n", b)
	fmt.Println("-----------------------------------")
	
	var c = "abc"
	b = c
	fmt.Printf("b的类型:%T\nb的值:%+[1]v\n", b)
}
================调试结果================
b的类型:int
b的值:500
-----------------------------------
b的类型:string
b的值:abc

4.3 为什么要用空接口

比如我有这样一个需求:我需要一个容器,该容器能存储任意类型的数据,并且能够混装,注意是能混装!
这咋整呢?用结构体吗?不行,结构体虽然能混装不同的数据类型,但它有个前提,必须要有明确的字段。
所以这种情况下只有空接口interface{}是最合适的。

var d = []any{1, 2.1, true, "abc"}
// []interface{},表示数据类型,后面的{}表示元素字面量定义
var e = []interface{}{1, 2.1, true, "abc"}
fmt.Printf("d的类型:%T|d的值:%+[1]v\n", d)
fmt.Printf("e的类型:%T|e的值:%+[1]v\n", e)
================调试结果================
d的类型:[]interface {}|d的值:[1 2.1 true abc] // 返回值为任意数据类型的切片
e的类型:[]interface {}|e的值:[1 2.1 true abc]

5. 接口类型断言

5.1 基本介绍

在 Go 语言中,当你有一个接口变量时,你可以通过 类型断言 来检查它实际存储的类型,注意只能配合接口使用。
类型断言的基本语法是 变量.(数据类型)。

5.2 示例

5.2.1 基本使用

var a = 500
var b interface{} = a

// v, ok := b.(int) 含义:把b接口中存储的数据当成int,可以吗?
// 可以:ok返回true,v返回对应的值
// 不可以:ok返回false,v返回对应数据类型的默认值(int:0,str: 空串)
v, ok := b.(int)
fmt.Printf("v=%v ok=%v\n", v, ok)

v1, ok1 := b.(string)
fmt.Printf("v=%v ok=%v\n", v1, ok1)
================调试结果================
v=500 ok=true
v= ok=false // 注意这里的v是空串

5.2.2 结合if判断

string类型,断言失败会返回空串,但万一默认就是空串呢?所以这里最好结合if一起使用,判断ok的值来判断类型断言是否成功,如下代码:

var a = 500
var b interface{} = a

if v, ok := b.(string); ok {
	fmt.Printf("断言成功,v的类型为:%T 值为:%[1]v", v)
} else {
	fmt.Printf("断言失败,v的类型为:%T 值为:%[1]v", v)
}
================调试结果================
断言失败,v的类型为:string 值为:

5.3 type-switch

当需要判断多个原始数据类型时,写if的话,需要写很长的代码,这里可以使用type-switch来替代if,更加简洁。
注意:type-switch只能配合接口类型断言使用。

var a = 500
var b interface{} = a

// b.(type)的意思,是取b接口中数据的类型
switch b.(type) { // 这个b.(type)可以用变量接住,值会赋值给该变量
case int:
	fmt.Println("int,值=",b)
case string:
	fmt.Println("string")
case bool:
	fmt.Println("bool")
case nil: // 在判断接口或指针类型时,最好加上nil判断,防止出现空接口或空指针
	fmt.Println("nil 空接口")
default:
	fmt.Println("其他")
}
================调试结果================
int,值= 500

6. 接口输出格式化

6.1 普通方式格式化

先看一段代码:

package main

import (
	"fmt"
)

type Sport interface {
	run()
	jump()
}

type Person struct {
	name string
	age  int
}

func (*Person) run() {
	fmt.Println("跑步")
}

func (*Person) jump() {
	fmt.Println("跳跃")
}

func (t *Person) swim() {
	fmt.Println("游泳")
}

func main() {
	var p = new(Person)
	fmt.Printf("%+v\n", p)
}
================调试结果================
&{name: age:0}

可以看到上述代码默认输出格式,看起来不太美观,可以如下优化一下:

package main

import (
	"fmt"
)

type Sport interface {
	run()
	jump()
}

type Person struct {
	name string
	age  int
}

func (*Person) run() {
	fmt.Println("跑步")
}

func (*Person) jump() {
	fmt.Println("跳跃")
}

func (t *Person) swim() {
	fmt.Println("游泳")
}

func Print(p *Person) string {
	return fmt.Sprintf("我叫%s,我的年龄是%d", p.name, p.age)
}

func NewPerson(name string, age int) *Person {
	return &Person{name, age}
}

func main() {
	var p = NewPerson("Tom", 21)

	fmt.Println(Print(p))
}
================调试结果================
我叫Tom,我的年龄是21

6.2 方法格式化

package main

import (
	"fmt"
)

type Sport interface {
	run()
	jump()
}

type Person struct {
	name string
	age  int
}

func (*Person) run() {
	fmt.Println("跑步")
}

func (*Person) jump() {
	fmt.Println("跳跃")
}

func (t *Person) swim() {
	fmt.Println("游泳")
}

func (p *Person) String() string {
	return fmt.Sprintf("我叫%s,我的年龄是%d", p.name, p.age)
}

func NewPerson(name string, age int) *Person {
	return &Person{name, age}
}

func main() {
	var p = NewPerson("Tom", 21)

	fmt.Printf("%+v\n", p)
	fmt.Printf("%v\n", p)
	fmt.Println(p)
	fmt.Print(p)
}
================调试结果================
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21
  • 16
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值