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

Golang接口断言学习

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,表示断言成功
  • 断言失败,okfalsetT的零值
  • 通常用于if else结构
  1. 方式二:t := i.(T)
  • 断言一个接口对象i里不是 nil,并且接口对象i存储的值的类型是 T
  • 如果断言成功,就会返回值给 t,如果断言失败,就会触发 panic
  • 通常用于switch结构
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!