Golang接口断言学习
在Golang中,空接口 interface{}
没有定义任何函数,因此Golang 中所有类型都实现了空接口。当一个函数的形参是interface{}
,那么在函数中,需要对形参进行断言,从而得到它的真实类型。
一.类型断言
在学习接口断言之前,先了解一下类型断言,其实接口断言也是在判断类型。
类型断言,通过它可以做到以下几件事情:
检查 i 是否为 nil
检查 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
,表示断言失败,此时t
为 T
的零值。
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
断言的逻辑也正常输出了。
总结一下,今天主要记录了接口如何断言的,通常有两种方式:
方式一: t, ok:= i.(T)
断言成功,就会返回其类型给 t
,并且此时ok
的值 为true
,表示断言成功断言失败, ok
为false
,t
为T
的零值通常用于 if else
结构
方式二: t := i.(T)
断言一个接口对象 i
里不是nil
,并且接口对象i
存储的值的类型是T
如果断言成功,就会返回值给 t
,如果断言失败,就会触发panic
通常用于 switch
结构
本文使用 mdnice 排版
有疑问加站长微信联系(非本文作者)