Golang: 什么时候nil != nil

Golang: 什么时候nil != nil最近在做 Go 语言开发工作中遇到不少坑 今天来理一理在哪些情况下我们使用 操作符号对比时候 nil nil 以及我们如何避免在代码中遇到这些问题 我们先定义两个不同类型的变量 每个变量都赋值为 nil vara int nilvarbinter nil 想象一下下面的代码会输出什么结果 fmt Println a nil a nil fmt Pr

最近在做Go语言开发工作中遇到不少坑,今天来理一理在哪些情况下我们使用 == 操作符号对比时候 nil != nil,以及我们如何避免在代码中遇到这些问题。

我们先定义两个不同类型的变量,每个变量都赋值为nil。

var a *int = nil

var b interface{} = nil

想象一下下面的代码会输出什么结果:

fmt.Println(“a == nil”, a == nil)

fmt.Println(“b == nil”, b == nil)

fmt.Println(“a == b:”, a == b)

运行结果:

a == nil: true

b == nil: true

a == b: false

我们再来看一个类似的例子,我们将b初始化为a

var a *int = nil

var b interface{} = a

fmt.Println(“a == nil:”, a == nil)

fmt.Println(“b == nil:”, b == nil)

fmt.Println(“a == b:”, a == b)

我们再次运行上面代码后,输出的结果如下

a == nil: true

b == nil: false

a == b: true

这是怎么回事?

这个问题不是Go语言bug,这只是一个特定的规则,我们理解这个规则后就能明白经常看到开源代码中写的下面这种代码是为什么了。

if nil == a {

b = nil

}

这里对b赋值之前对a进行了检测并且直接赋值了 nil。

我们需要搞清楚的第一件事,Go中的所有指针都有两个值,一个是类型(表明指针的类型),一个是值(指向具体值).也就是说每个指针都需要有一个类型来表明指针属于什么类型,所以我们不能给一个指针类型赋值为nil。例如下面这个代码就不能被编译

a := nil

为了让这行代码被编译,必须要给这个指针赋值一个类型,然后对它的值赋值为nil,就像这样:

var a *int = nil

var b interface{} = nil

现在这些变量都有了类型,就可以使用 fmt.Printf 函数打印出它们的类型了。

var a *int = nil

var b interface{} = nil

fmt.Printf(“a=(%T, …)\n”, a)

fmt.Printf(“b=(%T, …)\n”, b)

注: %T 格式化符仅仅打印出变量的类型

运行结果:

a=(*int, …)

b=(

, …)

上面的结果显示,当a被赋值为nil时候,它的类型被赋值为 *int;

b是一个interface{}(空接口),但是它被赋值nil时候它的类型却被指定为


这是怎么回事?

我们使用空接口主要是为了符合任何类型的实现。

其实也是一个类型,它符合一个空接口的标准。

好了,我们知道所有的指针都是由 (type, value)这样的组合构成,我们看到了给变量硬编码 nil时候会出现的情况。下面我们来给这些类型赋值,看会有什么变化。

var a *int = nil

var b interface{} = a

fmt.Printf(“a=(%T, …)\n”, a)

fmt.Printf(“b=(%T, …)\n”, b)

运行结果:

a=(*int, …)

b=(*int, …)

运行程序之后你会发现你会得到一个新的结果,b有了一个新的类型。

在我们之前硬编码赋值时候,b是一个

类型的指针。我们来看看使用a给b赋值时候,发生了什么。

比较a和b的值

既然我们了解这些类型被确定,让我们看看会发生什么,当我们在代码中检查a和b相等情况。我们开始对a和b都被分配给硬编码nil。之后,我们将会看到类似的片段,a赋值给b。

var a *int = nil

var b interface{} = nil

fmt.Printf(“a=(%T, %v)\n”, a, a)

fmt.Printf(“b=(%T, %v)\n”, b, b)

fmt.Println()

fmt.Println(“a == nil:”, a == nil)

fmt.Println(“b == nil:”, b == nil)

fmt.Println(“a == b:”, a == b)

运行结果:

a=(*int,

)

b=(

,

)

a == nil: true

b == nil: true

a == b: false

这里很奇怪的是 a和b是不相等的,这是一个很奇怪的状况,看起来像是 a == nil 且 b == nil,所以应该是 a == b,但是实际输出结果并不是这样。

实际上我们写的 a == nil 并不是真正的在比较值,我们实际上是在比较(type,value) 这个结构。我们并不是仅仅比较存储在a中的值.

下面模拟显示一下实际比较的值:

a == nil: (*int,

) == (*int*,

)

b == nil: (

,

) == (

,

)



a == b: (*int,

) == (

,

)


当我们看到上面这个就明白了,a和b实际上是不等的,因为它们的类型并不一样,但是在代码中这里并不会显示展示出来,所以我们会误解它们应该是相等的。

但是如果你这样写代码

if nil == a && nil == b {

//表达式为true

}

下面我们看一下将a赋值给b,会发生什么

var a *int = nil

var b interface{} = a // <- the change

fmt.Printf(“a=(%T, %v)\n”, a, a)

fmt.Printf(“b=(%T, %v)\n”, b, b)

fmt.Println()

fmt.Println(“a == nil:”, a == nil)

fmt.Println(“b == nil:”, b == nil)

fmt.Println(“a == b:”, a == b)

运行结果:

a=(*int,

)

b=(*int,

)

a == nil: true

b == nil: false

a == b: true

现在的结果又有问题了,b == nil 返回 false

当我们运行 b == nil的时候,编译器需要确定给nil一个什么类型,实际上编译器给出了

。但是实际上这个时候b被赋值为 <*int, nil>。明显编译器就不会认为他们是相等的了。

这个地方实际上会造成一些混淆,我们以为编译器会处理这个问题,但是实际上编译器并不能处理这个问题,因为interface{}的类型在运行的过程中随时在发生变化。

比如下面的程序

var a *int = nil

var b interface{} = a

var c *string = nil

fmt.Printf(“b=(%T, %v)\n”, b, b)

fmt.Println(“b == nil:”, b == nil)

b = c

fmt.Printf(“b=(%T, %v)\n”, b, b)

fmt.Println(“b == nil:”, b == nil)

b = nil

fmt.Printf(“b=(%T, %v)\n”, b, b)

fmt.Println(“b == nil:”, b == nil)

运行结果:

b=(*int,

)

b == nil: false

b=(*string,

)

b == nil: false

b=(

,

)

b == nil: true

从这个结果我们就可以看出来,编译器在编译的时候并不能确定b的类型,只能在运行期的时候确定b的具体类型,所以编译器并不能处理这个问题。

我们来看看如何强制让编译器将 nil放进正确类型里,实际上这并不是唯一的情况下让编译器使用这样的类型决定。比如,当你给变量分配一个硬编码的数字,编译器应该使用哪种类型将基于程序的上下文来决定。当声明的变量(如var int = 12),但这也会发生在当我们经过一个硬编码值函数或当我们给变量分配一个数字。所有这些情况如下所示。

程序如下:

package main

import “fmt”

func main() {

var a int = 12

var b float64 = 12

var c interface{} = a

d := 12 // will be an int

fmt.Printf(“a=(%T,%v)\n”, a, a)

fmt.Printf(“b=(%T,%v)\n”, b, b)

fmt.Printf(“c=(%T,%v)\n”, c, c)

fmt.Printf(“d=(%T,%v)\n”, d, d)

useInt(12)

useFloat(12)

}

func useInt(n int) {

fmt.Printf(“useInt=(%T,%v)\n”, n, n)

}

func useFloat(n float64) {

fmt.Printf(“useFloat=(%T,%v)\n”, n, n)

}

运行结果:

var a int = 12

var b float64 = 12

var c interface{} = a

fmt.Println(“a==12:”, a == 12) // true

fmt.Println(“b==12:”, b == 12) // true

fmt.Println(“c==12:”, c == 12) // true

fmt.Println(“a==c:”, a == c) // true

fmt.Println(“b==c:”, b == c) // false

有没有一种抓狂的感觉。a等于12,b等于12,c等于12,a等于c,但是c不等于a。怎么回事?

我们看看之前我们得出的结论:

a=(int,12)

b=(float64,12)

c=(int,12)

看到这个是不是有点明白怎么回事了?类型不匹配啊!!!

另外有一个有意思的事情就是,当你拿一个数字和一个接口相比较时候,这个接口的类型永远是int;类似的当你硬编码的nil相比较时候,这个时候类型永远是

.

var b float64 = 12

var c interface{} = b

fmt.Println(“c==12:”, c == 12)

fmt.Printf(“c=(%T,%v)\n”, c, c)

fmt.Printf(“hard-coded=(%T,%v)\n”, 12, 12)

运行结果:

c==12: false

c=(float64,12)

hard-coded=(int,12)

总结

当我们硬编码的值和变量编译器做==操作时候,假定他们有一些特定类型和遵循一些规则来实现,指针的数据结构是

,比较时候会比较这两个参数。有时候这个情况会比较困惑,但是你只要去适应它就好了。如果你发现各种类型都可以分配给nil,避免问题的一种常用技术是显式地指定为nil。,而不是 a = b,例如:

var a *int = nil

var b interface{}

if a == nil {

  b = nil

}

这样我们对b进行硬编码赋值nil时候才可以得到我们想要的效果。

最后举一个常出现的例子

type AAer interface {

That() int

}

type BB struct {

}

type BBMgr struct {

bbMap map[int]AAer

}

func (this *BB) That() int {

return 0

}

func (this *BBMgr) QueryAA(id int32) AAer {

aa, _ :=this.bbMap[id]

//这里如果没有找到数据 返回的接口去做 nil比较的话

//发现 AAer永远不为nil,但是执行AAer的函数就报错

// 实际上这里应该写

// aa, ok :=this.bbMap[id]

// if !ok {

// return nil

// }

return aa

}

Golang: 什么时候nil != nil

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229985.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午3:24
下一篇 2026年3月16日 下午3:24


相关推荐

  • Python十大算法之冒泡排序Python代码简单实现

    Python十大算法之冒泡排序Python代码简单实现Python 十大算法之冒泡排序 冒泡排序核心 两个两个数进行比较 小的放前边大的放后面 升序排序降序反之 定义一个函数 实现冒泡排序 defsort the list 由于需要两个两个的进行元素比较 每次比较都会把最大的一个值放到最后面 最后一轮比较可以省去 所以需要比较的次数为列表元素总数减一 foriinrange len list 1 每次进行一轮比较后 会把最大的元素放到最后 所以每轮进比较

    2026年3月19日
    2
  • k8s支持的存储_k8s安装与配置与优化

    k8s支持的存储_k8s安装与配置与优化k8sPV和PVC概述PVPVC生命周期配置存储ConfigMapSecretPV和PVC概述前面我们已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多,要求客户全部掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用,kubernetes引入了PV和PVC两种资源对象。PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由kubernetes管理员进行创

    2022年8月11日
    6
  • hbase 实战项目

    hbase 实战项目首先根据 hadoop 搭建 hbase 搭建把环境弄好由于 hbase 依赖于 hdfs 所以需要进入 hadoop sbin 下启动 start dfs sh start yarn sh 然后进 hbase gt 下启动 start hbase sh 如果后面运行失败报找不到 zookeeper 八成你需要进 hbase bin gt

    2026年3月18日
    2
  • XSS攻击及防御

    XSS攻击及防御本文来自 高爽 Coder 原文地址 http blog csdn net ghsau article details 转载请注明 XSS 又称 CSS 全称 CrossSiteScr 跨站脚本攻击 是 Web 程序中常见的漏洞 XSS 属于被动式且用于客户端的攻击方式 所以容易被忽略其危害性 其原理是攻击者向有 XSS 漏洞的网站中输入 传入 恶意的 H

    2026年3月20日
    0
  • 离散数学简单复习知识点汇总

    离散数学简单复习知识点汇总命题是非真必假的陈述句 联结词 与原子命题一起构成复合命题 原子命题公式 命题常元 命题变元同城为原子命题公式 简称原子公式 合式公式是由下列规则形成的字符串 1 真值 T 和 F 是合式公式 2 原子命题公式是一个合式公式 3 若 A 是合式公式 则 A 是合式公式 4 若 A 和 B 是合式公式 则 A B A B A

    2026年3月19日
    2
  • (转)pyCharm最新2017激活码

    (转)pyCharm最新2017激活码来源:pyCharm最新2017激活码EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVFbWFpbCI6IiIsImxpY2Vuc2VSZXN0cmljdGlvbiI6IkZvciBlZHVjYXRpb25hbCB1c2U

    2022年8月27日
    56

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号