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

Go 学习笔记(89) — 接口类型变量的等值比较操作(nil 接口变量、空接口类型变量、非空接口类型变量)

1. nil 接口变量

未赋初值的接口类型变量的值为 nil,这类变量也就是 nil 接口变量,我们来看这类变量的内部表示输出的例子:


func printNilInterface() {
  // nil接口变量
  var i interface{} // 空接口类型
  var err error     // 非空接口类型
  println(i)
  println(err)
  println("i = nil:", i == nil)
  println("err = nil:", err == nil)
  println("i = err:", i == err)
}

运行这个函数,输出结果是这样的:


(0x0,0x0)
(0x0,0x0)
i = nil: true
err = nil: true
i = err: true

我们看到,无论是空接口类型还是非空接口类型变量,一旦变量值为 nil,那么它们内部表示均为(0x0,0x0),也就是类型信息、数据值信息均为空。因此上面的变量 ierr 等值判断为 true

2. 空接口类型变量

下面是空接口类型变量的内部表示输出的例子:


  func printEmptyInterface() {
      var eif1 interface{} // 空接口类型
      var eif2 interface{} // 空接口类型
      var n, m int = 17, 18
  
      eif1 = n
      eif2 = m

      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // false
  
      eif2 = 17
      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // true
 
      eif2 = int64(17)
      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // false
 }

这个例子的运行输出结果是这样的:


eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0xc00007ef40)
eif1 = eif2: false
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0x10eb3d0)
eif1 = eif2: true
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac640,0x10eb3d8)
eif1 = eif2: false

我们按顺序分析一下这个输出结果。

首先,代码执行到第 11 行时,eif1eif2 已经分别被赋值整型值 17 与 18,这样 eif1eif2 的动态类型的类型信息是相同的(都是 0x10ac580),但 data 指针指向的内存块中存储的值不同,一个是 17,一个是 18,于是 eif1 不等于 eif2

接着,代码执行到第 16 行的时候,eif2 已经被重新赋值为 17,这样 eif1eif2 不仅存储的动态类型的类型信息是相同的(都是 0x10ac580),data 指针指向的内存块中存储值也相同了,都是 17,于是 eif1 等于 eif2

然后,代码执行到第 21 行时,eif2 已经被重新赋值了 int64 类型的数值 17。这样,eif1eif2 存储的动态类型的类型信息就变成不同的了,一个是 int,一个是 int64,即便 data 指针指向的内存块中存储值是相同的,最终 eif1eif2 也是不相等的。

从输出结果中我们可以总结一下:对于空接口类型变量,只有 _typedata 所指数据内容一致的情况下,两个空接口类型变量之间才能划等号。另外,Go 在创建 eface 时一般会为 data 重新分配新内存空间,将动态类型变量的值复制到这块内存空间,并将 data 指针指向这块内存空间。因此我们多数情况下看到的 data 指针值都是不同的。

3. 非空接口类型变量

我们也直接来看一个非空接口类型变量的内部表示输出的例子:


type T int

func (t T) Error() string { 
    return "bad error"
}

func printNonEmptyInterface() { 
    var err1 error // 非空接口类型
    var err2 error // 非空接口类型
    err1 = (*T)(nil)
    println("err1:", err1)
    println("err1 = nil:", err1 == nil)

    err1 = T(5)
    err2 = T(6)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)

    err2 = fmt.Errorf("%d\n", 5)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)
}   

这个例子的运行输出结果如下:


err1: (0x10ed120,0x0)
err1 = nil: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed1a0,0x10eb318)
err1 = err2: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed0c0,0xc000010050)
err1 = err2: false

我们看到上面示例中每一轮通过 println 输出的 err1err2tabdata 值,要么 data 值不同,要么 tabdata 值都不同。

和空接口类型变量一样,只有 tabdata 指的数据内容一致的情况下,两个非空接口类型变量之间才能划等号。这里我们要注意 err1 下面的赋值情况:


err1 = (*T)(nil)

针对这种赋值,println 输出的 err1 是(0x10ed120, 0x0),也就是非空接口类型变量的类型信息并不为空,数据指针为空,因此它与 nil(0x0,0x0)之间不能划等号。


type MyError struct {
    error
}

var ErrBad = MyError{
    error: errors.New("bad things happened"),
}

func bad() bool {
    return false
}

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = &ErrBad
    }
    return p
}

func main() {
    err := returnsError()
    if err != nil {
        fmt.Printf("error occur: %+v\n", err)
        return
    }
    fmt.Println("ok")
}

现在我们再回到我们开头的那个问题,你是不是已经豁然开朗了呢?开头的问题中,从 returnsError 返回的 error 接口类型变量 err 的数据指针虽然为空,但它的类型信息(iface.tab)并不为空,而是 *MyError 对应的类型信息,这样 errnil(0x0,0x0)相比自然不相等,这就是我们开头那个问题的答案解析,现在你明白了吗?

4. 空接口类型变量与非空接口类型变量的等值比较

下面是非空接口类型变量和空接口类型变量之间进行比较的例子:


func printEmptyInterfaceAndNonEmptyInterface() {
  var eif interface{} = T(5)
  var err error = T(5)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)

  err = T(6)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)
}

这个示例的输出结果如下:


eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4d8)
eif = err: true
eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4e0)
eif = err: false

你可以看到,空接口类型变量和非空接口类型变量内部表示的结构有所不同(第一个字段:_type vs. tab),两者似乎一定不能相等。但 Go 在进行等值比较时,类型比较使用的是 eface_typeifacetab._type,因此就像我们在这个例子中看到的那样,当 eiferr 都被赋值为T(5)时,两者之间是划等号的。

参考:https://time.geekbang.org/column/article/473414

相关文章:

  • dubbo源码解析之服务调用(通信)流程
  • Linux网络技术学习(四)—— 用户空间与内核的接口
  • Django--ORM 多表查询
  • pytest 运行方式、常用参数、前后置条件
  • MySQL-1-SQL讲解
  • 数据结构与算法之美读书笔记15
  • msf辅助模块详细操作
  • 【移动端网页特效】02-移动端轮播图(原生JS)
  • 神经网络(十二)卷积神经网络DLC
  • vue3.x 组件间传参
  • Tomcat域名访问文件出现访问不到的问题
  • BATJ 互联网公司面试必问知识点:Spring 全家桶全解,java 分布式框架技术方案
  • RabbitMQ(一)消息队列
  • Pycharm打开时一直加载中?解决办法来了~
  • Spring Security 如何防止 Session Fixation 攻击
  • 网络传输文件的问题
  • Joomla 2.x, 3.x useful code cheatsheet
  • leetcode386. Lexicographical Numbers
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • react 代码优化(一) ——事件处理
  • vue脚手架vue-cli
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 爱情 北京女病人
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 全栈开发——Linux
  • 设计模式 开闭原则
  • 微信小程序开发问题汇总
  • 项目管理碎碎念系列之一:干系人管理
  • 应用生命周期终极 DevOps 工具包
  • 最近的计划
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #预处理和函数的对比以及条件编译
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Java数据结构)ArrayList
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (二)换源+apt-get基础配置+搜狗拼音
  • (分布式缓存)Redis持久化
  • (转)LINQ之路
  • ***详解账号泄露:全球约1亿用户已泄露
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET/C# 检测电脑上安装的 .NET Framework 的版本
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • .NET使用存储过程实现对数据库的增删改查
  • .NET性能优化(文摘)
  • @GetMapping和@RequestMapping的区别
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • [APIO2015]巴厘岛的雕塑
  • [Asp.net mvc]国际化
  • [bzoj2957]楼房重建
  • [C#] 基于 yield 语句的迭代器逻辑懒执行
  • [C/C++]_[初级]_[关于编译时出现有符号-无符号不匹配的警告-sizeof使用注意事项]