当前位置: 首页 > news >正文

Go基础系列:接口类型断言和type-switch

接口类型探测:类型断言

接口实例中存储了实现接口的类型实例,类型的实例有两种:值类型实例和指针类型实例。在程序运行过程中,接口实例存储的实例类型可能会动态改变。例如:

// ins是接口实例
var ins Shaper

// ins存储值类型的实例
ins = c1

// 一段时间后...
...

// ins存储指针类型的实例,存储的类型发生改变
ins = c2

// 一段时间后...

// ins可能存储另一个类型实例
ins = s1

所以,需要一种探测接口实例所存储的是值类型还是指针类型。

探测的方法是:ins.(Type)ins.(*Type)。它们有两个返回值,第二个返回值是ok返回值,布尔类型,第一个返回值是探测出的类型。也可以只有一个返回值:探测出的类型。

// 如果ins保存的是值类型的Type,则输出
if t, ok := ins.(Type); ok {
    fmt.Printf("%T\n", v)
}

// 如果ins保存的是指针类型的*Type,则输出
if t, ok := ins.(*Type); ok {
    fmt.Printf("%T\n", v)
}

// 一个返回值的探测
t := ins.(Type)
t := ins.(*Type)

以下是一个例子:

package main

import "fmt"

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Square struct类型
type Square struct {
    length float64
}

// Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    var ins1, ins2 Shaper

    // 指针类型的实例
    s1 := new(Square)
    s1.length = 3.0
    ins1 = s1
    if v, ok := ins1.(*Square); ok {
        fmt.Printf("ins1: %T\n", v)
    }

    // 值类型的实例
    s2 := Square{4.0}
    ins2 = s2
    if v, ok := ins2.(Square); ok {
        fmt.Printf("ins2: %T\n", v)
    }
}

上面两个Printf都会输出,因为它们的类型判断都返回true。如果将ins2.(Square)改为ins2.(*Square),第二个Printf将不会输出,因为ins2它保存的是值类型的实例。

以下是输出结果:

ins1: *main.Square
ins2: main.Square

特别需要注意的是,ins必须明确是接口实例。例如,以下前两种声明是有效的,第三种推断类型是错误的,因为它可能是接口实例,也可能是类型的实例副本。

var ins Shaper     // 正确
ins := Shaper(s1)  // 正确
ins := s1          // 错误

当ins不能确定是接口实例时,用它来进行测试,例如ins.(Square)将会报错:

invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)

它说明了左边的ins是非接口类型(non-interface type)。

另一方面,通过接口类型断言(ins.(Type)),如果Type是一个接口类型,就可以判断接口实例ins中所保存的类型是否也实现了Type接口。例如:

var r io.Read
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

var w io.Writer
w = r.(io.Writer)

上面的r是io.Read接口的一个实例变量,它里面保存的是tty和它的类型,即(tty, *os.File),然后断言r的类型,探测它里面的类型*File是否也实现了io.Writer接口,如果实现了,则保存到io.Writer接口的实例变量w中,这样w实例也将保存(tty,*os.File)

由于任意内容都实现了空接口,所以,总是可以把一个接口实例无需通过任何断言地赋值给一个空接口实例:

var empty interface{}
empty = w

现在empty也保存了(tty,*os.File)

type Switch结构

switch流程控制结构还可以用来探测接口实例保存的类型。这种结构称为type-switch

用法如下:

switch v := ins.(type) {
case *Square:
    fmt.Printf("Type Square %T\n", v)
case *Circle:
    fmt.Printf("Type Circle %T\n", v)
case nil:
    fmt.Println("nil value: nothing to check?")
default:
    fmt.Printf("Unexpected type %T", v)
}

其中ins.(type)中的小写type是固定的词语。

以下是一个使用示例:

package main

import (
    "fmt"
)

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Circle struct类型
type Circle struct {
    radius float64
}

// Circle类型实现Shaper中的方法Area()
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

// Square struct类型
type Square struct {
    length float64
}

// Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    s1 := &Square{3.3}
    whichType(s1)

    s2 := Square{3.4}
    whichType(s2)

    c1 := new(Circle)
    c1.radius = 2.3
    whichType(c1)
}

func whichType(n Shaper) {
    switch v := n.(type) {
    case *Square:
        fmt.Printf("Type Square %T\n", v)
    case Square:
        fmt.Printf("Type Square %T\n", v)
    case *Circle:
        fmt.Printf("Type Circle %T\n", v)
    case nil:
        fmt.Println("nil value: nothing to check?")
    default:
        fmt.Printf("Unexpected type %T", v)
    }
}

上面的type-switch中,之所以没有加上case Circle,是因为Circle只实现了指针类型的receiver,根据Method Set对接口的实现规则,只有指针类型的Circle示例才算是实现了接口Shaper,所以将值类型的示例case Circle放进type-switch是错误的。

相关文章:

  • PHP多进程
  • SpringMVC基础知识
  • MPAndroidChart 教程:Y轴 YAxis
  • 大快搜索数据爬虫技术实例安装教学篇
  • js递归,无限分级树形折叠菜单
  • Linux环境搭建及命令
  • 区块链教程Fabric1.0源代码分析Peer peer chaincode命令及子命令实现
  • 我的前端工具集(九)树工具重新封装和修改
  • Spring Cloud 2.x系列之网关zuul入门(三)
  • Can't create/write to file '/tmp/MLjnvU95' (Errcode: 13 - Permission denied)
  • 互融云保理业务系统助力企业快速拓展业务
  • 如何利用MongoDB打造TOP榜小程序
  • 4.时间复杂度和空间复杂度-2
  • 你真的懂Redis事务吗?
  • MySQL-去重留一
  • 30秒的PHP代码片段(1)数组 - Array
  • CSS中外联样式表代表的含义
  • Docker容器管理
  • HTML5新特性总结
  • JS 面试题总结
  • JS基础之数据类型、对象、原型、原型链、继承
  • Js基础知识(四) - js运行原理与机制
  • js面向对象
  • Lucene解析 - 基本概念
  • python docx文档转html页面
  • React-redux的原理以及使用
  • 聊聊sentinel的DegradeSlot
  • 入口文件开始,分析Vue源码实现
  • 我的面试准备过程--容器(更新中)
  • 我是如何设计 Upload 上传组件的
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • #laravel 通过手动安装依赖PHPExcel#
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • (8)STL算法之替换
  • (大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量
  • (四)模仿学习-完成后台管理页面查询
  • (原創) 未来三学期想要修的课 (日記)
  • (转)LINQ之路
  • (转)母版页和相对路径
  • .bashrc在哪里,alias妙用
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET Micro Framework初体验(二)
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net 后台导出excel ,word
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET建议使用的大小写命名原则
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [20180224]expdp query 写法问题.txt
  • [BROADCASTING]tensor的扩散机制
  • [iOS]中字体样式设置 API
  • [JAVA设计模式]第二部分:创建模式
  • [leetcode]_Symmetric Tree