【go】类型断言

接口-类型断言

Type Assertion

Type Assertion(中文名叫:类型断言),通过它可以做到以下几件事情

  1. 检查 i 是否为 nil(是nil直接抛出panic)
  2. 检查 i 存储的值是否为某个类型

具体的使用方式有两种:

第一种:

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

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

来写段代码试验一下

go 复制代码
package main

import "fmt"

func main() {
    var i interface{} = 10
    t1 := i.(int)
    fmt.Println(t1)

    fmt.Println("=====分隔线=====")

    t2 := i.(string)
    fmt.Println(t2)
}

运行后输出如下,可以发现在执行第二次断言的时候失败了,并且触发了 panic,可以发现断言成功的返回值是这个接口对象i存储的值。

go 复制代码
10
=====分隔线=====
panic: interface conversion: interface {} is int, not string

goroutine 1 [running]:
main.main()
        d:/Goworks/src/尚硅谷/面向对象/类型断言/demo01.go:12 +0xa8
exit status 2

如果要断言的接口值是 nil,那我们来看看也是不是也如预期一样会触发panic

go 复制代码
package main

func main() {
    var i interface{} // nil
    var _ = i.(interface{})
}

输出如下,确实是会 触发 panic

go 复制代码
panic: interface conversion: interface is nil, not interface {}

goroutine 1 [running]:
main.main()
        d:/Goworks/src/尚硅谷/面向对象/类型断言/demo02.go:5 +0x1a
exit status 2

第二种

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

和上面一样,这个表达式也是可以断言一个接口对象(i)里不是 nil,并且接口对象(i)存储的值的类型是 T,如果断言成功,就会返回其值给 t,并且此时 ok 的值 为 true,表示断言成功。

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

稍微修改下上面的例子,如下

go 复制代码
package main

import "fmt"

func main() {
    var i interface{} = 10
    t1, ok := i.(int)
    fmt.Printf("%d-%t\n", t1, ok)

    fmt.Println("=====分隔线1=====")

    t2, ok := i.(string)
    fmt.Printf("%s-%t\n", t2, ok)

    fmt.Println("=====分隔线2=====")

    var k interface{} // nil
    t3, ok := k.(interface{})
    fmt.Println(t3, "-", ok)

    fmt.Println("=====分隔线3=====")
    k = 10
    // 这个是判断是否为nil,为nil,ok为false,不为nil,ok为true
    t4, ok := k.(interface{})
    fmt.Printf("%d-%t\n", t4, ok)

    t5, ok := k.(int)
    fmt.Printf("%d-%t\n", t5, ok)
}

运行后输出如下,可以发现在执行第二次断言的时候,虽然失败了,但并没有触发了 panic。

go 复制代码
10-true
=====分隔线1=====
-false
=====分隔线2=====
<nil> - false
=====分隔线3=====
10-true
10-true

上面这段输出,你要注意的是第二个断言的输出在-false 之前并不是有没有输出任何 t2 的值,而是由于断言失败,所以 t2 得到的是 string 的零值也是 "" ,它是零长度的,所以你看不到其输出。

Type Switch

如果需要区分多种类型,可以使用 type switch 断言,这个将会比一个一个进行类型断言更简单、直接、高效。

go 复制代码
package main

import "fmt"

// 传任何一个对象就可以
func findType(i interface{}) {
    switch x := i.(type) {
    case int:
        fmt.Println(x, "is int")
    case string:
        fmt.Println(x, "is string")
    case nil:
        fmt.Println(x, "is nil")
    default:
        fmt.Println(x, "not type matched")
    }
}

func main() {
    findType(10)      // int
    findType("hello") // string

    var k interface{} // nil
    findType(k)

    findType(10.23) //float64
}

输出如下

go 复制代码
10 is int
hello is string
<nil> is nil
10.23 not type matched

额外说明一下:

  • 如果你的值是 nil,那么匹配的是 case nil
  • 如果你的值在 switch-case 里并没有匹配对应的类型,那么走的是 default 分支

详解:

1. 接口的本质
  • 在 Go 中,接口(interface{})是一种可以存储任何类型值的类型。
  • 接口值实际上由两部分组成:
    • 动态类型:实际存储的值的类型。
    • 动态值:实际存储的值本身。
  • 当你将一个具体的值赋给接口变量时,Go 会将该值的类型信息和值本身打包存入接口中。

例如:

go 复制代码
var i interface{} = 10

在这个例子中:

  • 动态类型是 int
  • 动态值是 10
2. 类型断言与类型开关
  • 类型断言(i.(T))用于从接口值中提取具体的值,并将其转换为指定的类型 T
  • 类型开关(switch x := i.(type))则允许你动态地检查接口值的实际类型,并根据类型执行不同的逻辑。

在你的代码中:

go 复制代码
switch x := i.(type) {
case int:
    fmt.Println(x, "is int")
case string:
    fmt.Println(x, "is string")
case nil:
    fmt.Println(x, "is nil")
default:
    fmt.Println(x, "not type matched")
}
  • x := i.(type) 是类型开关的核心语法。
  • 在每个 case 分支中,x 的类型会被自动转换为匹配的类型(如 intstring 等)。
  • 如果没有匹配到任何类型,则进入 default 分支。
3. 类型断言后的静态类型
  • 当类型断言成功时,返回的值的静态类型会变为断言的目标类型。
  • 例如,在 case int: 分支中,x 的静态类型是 int,而在 case string: 分支中,x 的静态类型是 string
  • 这是因为类型断言本质上是一个显式的类型转换过程,Go 会在运行时检查接口值的实际类型,并将值转换为目标类型。
4. 原来的静态类型
  • 在调用 findType 函数时,传入的参数是接口类型 interface{}
  • 无论传入的具体值是什么类型(如 intstring 或其他类型),它们都会被隐式地包装成接口类型 interface{}
  • 换句话说,函数参数 i 的静态类型始终是 interface{},但它的动态类型和动态值取决于调用时传入的具体值。
5. 隐式转换
  • Go 的类型系统通过接口实现了隐式转换。
  • 当你将一个具体类型的值传递给一个接口类型的变量时,Go 会自动将该值包装为接口类型。
  • 在类型断言或类型开关中,Go 再次将接口值解包,恢复其原始的动态类型和值。
总结
  1. 类型断言完成后,返回的是静态类型为断言类型的对象
    是的,类型断言完成后,返回的值的静态类型会变为断言的目标类型。例如,在 case int: 分支中,x 的静态类型是 int
  2. 原来的静态类型是空接口类型(interface{}
    是的,在调用 findType 函数时,传入的参数被隐式地包装为 interface{} 类型,因此它的静态类型是 interface{}
  3. 这是 Go 的隐式转换机制
    是的,Go 的接口机制使得具体类型的值可以被隐式地包装为接口类型,并在需要时通过类型断言或类型开关解包为具体类型。

此外

还有两点需要你格外注意

  1. 类型断言,仅能对静态类型为空接口(interface{})的对象进行断言,否则会抛出错误,具体内容可以参考:关于接口的三个"潜规则"
  2. 类型断言完成后,实际上会返回静态类型为你断言的类型的对象,而要清楚原来的静态类型(原来的x)为空接口类型(interface{}),这是 Go 的隐式转换。
相关推荐
风中飘爻34 分钟前
JavaScript:BOM编程
开发语言·javascript·ecmascript
kyle~35 分钟前
ROS2---std_msgs基础消息包
开发语言·python·机器人·ros·机器人操作系统
满怀101536 分钟前
【NumPy科学计算引擎:从基础操作到高性能实践】
开发语言·python·numpy
我命由我123451 小时前
35.Java线程池(线程池概述、线程池的架构、线程池的种类与创建、线程池的底层原理、线程池的工作流程、线程池的拒绝策略、自定义线程池)
java·服务器·开发语言·jvm·后端·架构·java-ee
&zzz2 小时前
Python生成exe
开发语言·python
Chandler242 小时前
Go:方法
开发语言·c++·golang
CopyLower2 小时前
分布式ID生成方案的深度解析与Java实现
java·开发语言·分布式
随便@_@3 小时前
基于MATLAB/simulink的信号调制仿真--AM调制
开发语言·matlab·simulink·移动仿真
爱代码的小黄人4 小时前
深入解析系统频率响应:通过MATLAB模拟积分器对信号的稳态响应
开发语言·算法·matlab
vsropy4 小时前
matlab安装python API 出现Invalid version: ‘R2022a‘,
开发语言·python