27

Golang学习——interface接口学习(二)

 3 years ago
source link: https://studygolang.com/articles/28503
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Golang接口断言学习

    • 1.if else结构 接口断言

    • 2.switch结构 接口断言

Golang 中,空接口 interface{} 没有定义任何函数,因此 Golang 中所有类型都实现了空接口。当一个函数的形参是 interface{} ,那么在函数中,需要对形参进行断言,从而得到它的真实类型。

一.类型断言

在学习接口断言之前,先了解一下类型断言,其实接口断言也是在判断类型。

类型断言,通过它可以做到以下几件事情:

  1. 检查 i 是否为 nil
  2. 检查 i 存储的值是否为 某个类型

通常有两种方式:

第一种:

t := i.(T) 复制代码

这个表达式可以断言一个接口对象 i 里不是 nil ,并且接口对象 i 存储的值的类型是 T ,如果断言成功,就会返回值给 t ,如果断言失败,就会触发 panic

t := i.(T) 常用于 switch 结构。

第二种:

t, ok:= i.(T) 复制代码

这个表达式也是可以断言一个接口对象 t 里不是 nil ,并且接口对象 t 存储的值的类型是 T ;

如果断言成功,就会返回其类型给 t ,并且此时 ok 的值 为 true ,表示断言成功。

如果接口值的类型,并不是我们所断言的 T ,就会断言失败,但和第一种表达式不同的事,这个不会触发 panic ,而是将 ok 的值设为 false ,表示断言失败,此时 tT 的零值。

t, ok:= i.(T) 常用于 if else 结构。

二.接口断言

1.if else结构 接口断言

t, ok := i.(T) 断言在上一小节已经介绍过了,本小节,我们通过实战加深下理解。

我们先创建一个 Shape 形状接口,两个结构体。

// 定义接口
type Shape interface {
 perimeter() float64 // 返回形状的周长
 area() float64      // 返回形状的面积
}

// 定义结构体
type Circle struct {
 radius float64
}

type Triangle struct {
 a, b, c float64
}
复制代码

其中, Shape 接口有两个方法,分别是求形状的周长和面积。

两个结构体分别定义了自己独有的属性:

  • Circle (圆),定义了半径
  • Triangle (三角形),定义了三条边

接下来,我们实现 Shape 接口中的方法:

// 圆结构体 实现接口方法
func (c Circle) perimeter() float64 {
 return c.radius * math.Pi * 2
}

func (c Circle) area() float64 {
 return math.Pow(c.radius, 2) * math.Pi
}

// 三角形结构体 实现接口方法
func (t Triangle) perimeter() float64 {
 return t.a + t.b + t.c
}
func (t Triangle) area() float64 {
 p := t.perimeter() / 2
 return math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))
}
复制代码

其中三角形的面积计算使用了 海伦公式

接下来我们封装一个接口断言函数:

// 定义接口断言函数
func getInterfaceType(s Shape) {
 if ins, ok := s.(Triangle); ok {
  fmt.Println("是三角形,三边分别为:", ins.a, ins.b, ins.c)
 } else if ins, ok := s.(Circle); ok {
  fmt.Println("是圆形,半径为;", ins.radius)
 } else if ins, ok := s.(*Circle); ok {
  fmt.Printf("是圆形结构体指针,类型为:%T,存储的地址为:%p,指针自身的地址为:%p\n", ins, &ins, ins)
 } else {
  fmt.Println("无法判断类型...")
 }
}
复制代码

该函数中不仅判断了值传递的类型,也判断了引用传递(指针类型)的类型。因为 Struct 是值类型,所以我们加入引用类型,使练习更严谨一点。

接下来开始初始化结构体:

// 初始化一个圆结构体
c1 := Circle{radius: 10}
fmt.Println("==================圆结构体:==================")
fmt.Println("圆的周长为:", c1.perimeter())
fmt.Println("圆的面积为:", c1.area())

// 初始化一个三角形结构体
t1 := Triangle{
 a: 3,
 b: 4,
 c: 5,
}
fmt.Println("================三角形结构体:=================")
fmt.Println("三角形的周长为:", t1.perimeter())
fmt.Println("三角形的面积为:", t1.area())

// 初始化一个圆形结构体指针
var c2 *Circle = &Circle{radius: 5}
fmt.Println("================圆形结构体指针:===============")
fmt.Println("圆的周长为:", c2.perimeter())
fmt.Println("圆的面积为:", c2.area())
复制代码

输出:

==================圆结构体:==================
圆的周长为: 62.83185307179586
圆的面积为: 314.1592653589793
================三角形结构体:=================
三角形的周长为: 12
三角形的面积为: 6
================圆形结构体指针:===============
圆的周长为: 31.41592653589793
圆的面积为: 78.53981633974483
复制代码

可以看到,以上结构体都实现了 Shape 接口, 接下来开始进行接口断言:

fmt.Println("==============t, ok:= i.(T) 开始接口断言====================")
getInterfaceType(c1) // 判断该接口是否为 圆形结构体类型
getInterfaceType(t1) // 判断该接口是否为 圆形结构体类型
getInterfaceType(c2) // 判断该接口是否为 圆形结构体指针类型
复制代码

输出:

==============t, ok:= i.(T) 开始接口断言===================
是圆形,半径为; 10
是三角形,三边分别为: 3 4 5
是圆形结构体指针,类型为:*main.Circle,存储的地址为:0xc000006030,指针自身的地
址为:0xc0000140e0
复制代码

可以看到,我们的接口断言奏效了,并且输出了对应逻辑的结果。

2.switch结构 接口断言

断言其实还有另一种形式,就是用在利用 switch 语句判断接口的类型。

每一个 case 会被顺序地考虑。当命中一个 case 时,就会执行 case 中的语句。

因此 case 语句的顺序是很重要的,因为很有可能会有多个 case 匹配的情况。

我们再封装一个 switch 逻辑的接口断言函数,逻辑和之前的一模一样,只是条件语句换成了 switch....case

// 定义接口断言函数,使用 switch
func getInterfaceTypeSwitch(s Shape) {
 switch ins := s.(type) { // 首字母小写的 type
 case Circle:
  fmt.Println("是圆形,半径为;", ins.radius)
 case Triangle:
  fmt.Println("是三角形,三边分别为:", ins.a, ins.b, ins.c)
 case *Circle:
  fmt.Printf("是圆形结构体指针,类型为:%T,存储的地址为:%p,指针自身的地址为:%p\n", ins, &ins, ins)
 default:
  fmt.Println("无法判断类型...")
 }
}
复制代码

接下来测试封装的函数:

fmt.Println("==============t := i.(type) 开始接口断言====================")
getInterfaceTypeSwitch(c1) // 判断该接口是否为 圆形结构体类型
getInterfaceTypeSwitch(t1) // 判断该接口是否为 圆形结构体类型
getInterfaceTypeSwitch(c2) // 判断该接口是否为 圆形结构体指针类型
复制代码

输出:

==============t := i.(type) 开始接口断言====================
是圆形,半径为; 10
是三角形,三边分别为: 3 4 5
是圆形结构体指针,类型为:*main.Circle,存储的地址为:0xc000006038,指针自身的地
址为:0xc0000140e0
复制代码

可以看到, switch 断言的逻辑也正常输出了。

总结一下,今天主要记录了接口如何断言的,通常有两种方式:

  1. 方式一: t, ok:= i.(T)
  • t
    ok
    true
    
  • ok
    false
    t
    T
    
  • 通常用于 if else 结构
  1. 方式二: t := i.(T)
  • i
    nil
    i
    T
    
  • 如果断言成功,就会返回值给 t ,如果断言失败,就会触发 panic
  • 通常用于 switch 结构

本文使用 mdnice 排版


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK