前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你真的知道 GO 中 nil 代表什么吗?

你真的知道 GO 中 nil 代表什么吗?

作者头像
阿兵云原生
发布2023-09-29 10:07:40
4140
发布2023-09-29 10:07:40
举报
文章被收录于专栏:golang云原生new

本篇文章主要是来聊聊 Golang 中关于 nil 的使用方式及理解,看看有没有你还不知道的情况呢?

使用 Golang 的朋友都知道,在 Golang 的世界里面,有一个预先声明的标识符 nil

nil 标识符可以作为多种数据结构的零值,通常我们会将 nil 就认为是空的意思,就像 C 语言里面的 NULL 一样

此处说到零值,他其实就是一种数据类型还没有被初始化的时候的默认值,对应着的就是一个零值

例如:

整形的零值,是 0

字符串的零值是 ""

那么布尔类型的零值自然就是 false

零值默认为 nil 的数据结构可多了,有这些:

  • 函数
  • 指针
  • interface{}
  • Map
  • 切片 slice
  • 通道 channel

接下来分别从零值为 nil 的几种数据类型来聊聊的 nil 的那些事

nil 和 true/false 一样,不是 Golang 的关键字

nil 和 一般我们知道的布尔类型的值(true/false)类似,都不是 Golang 的关键字

我们可以将 nil,true,或者 false 作为变量的名字,并进行赋值和输出

代码语言:javascript
复制
func main() {
   log.SetFlags(log.Lshortfile)
   nil := 123
   true := 111
   false := 222
   log.Printf("nil == %+v,true == %+v,false==%+v", nil,true,false)
}

自然,例如 const 是 Golang 中的关键字,我们就没有办法将 const 作为变量名

nil 占用的空间因不同的数据结构而不同

在 C 语言中,我们知道可以通过 sizeof 去查看指针占用的空间,可能是 4 字节,也有可能是 8 字节,一般来说这是对应着 32 位系统和 64 位系统

例如一个空指针,也是会占用空间的,表示他是一个指针,指针的指向是 NULL

那么对应到 Golang 中,以 nil 作为零值的数据结构,同样有自己所占用的空间,占用空间的大小也是不一样的,Golang 中可以使用 unsafe 包中的 Sizeof 方法来进行查看

代码语言:javascript
复制
func main() {
   log.SetFlags(log.Lshortfile)


   var ptr *int = nil
   log.Println("nil 指针:",unsafe.Sizeof(ptr))

   var in interface{} = nil
   log.Println("nil interface{}:",unsafe.Sizeof(in))

   var mp map[string]string = nil
   log.Println("nil map:",unsafe.Sizeof(mp))

   var sli []int = nil
   log.Println("nil slice:",unsafe.Sizeof(sli))

   var ch chan string = nil
   log.Println("nil channel:",unsafe.Sizeof(ch))

   var fun func() = nil
   log.Println("nil 函数:",unsafe.Sizeof(fun))

}

此处可以看到,对于同一个系统,咱们指针的占用空间 C 语言和 Golang 是一样的(都是 8 字节),对于切片,map 等数据结构 nil 的大小,也与他们自身的底层数据结构有关,对于每一个数据结构的底层细节,可以看到文末的历史文章

切片零值 nil

我们知道,切片的底层数据结构是,一个指针 ptr,一个 cap 表示切片容量,一个 len 表示切片中已有数据的长度

所以,看到这里,对于理解切片的 nil 为什么占用空间是 24 字节,就明白了吧

  • 一个指针占用 8 字节
  • 一个 cap int 类型,占用 8 字节
  • 一个 len int 类型,占用 8 字节

对于一个空切片,使用的时候,需要注意不能去取索引对应的值,因为对于一个空切片来说,根本不存在,若访问这一片内存,则会报 panic: index out of range 数组越界

我们对于一个 nil 的切片,可以去取地址,可以取长度,可以取容量,自然也是可以使用 append() 来追加数据的

使用 append 来追加数据就会涉及到扩容,此处就不过多赘述了,详情可以查看关于 slice 的原理介绍

map 零值 nil

对于 map 也是会存在同样的问题,我们去取 map 中的某一个节点的值的时候

如果 map 之前是经过初始化的,那么我们访问一个不存在的 key 是没有问题的,且我们一般去访问 map 中的值的时候会比较谨慎,例如:

代码语言:javascript
复制
func main() {
   log.SetFlags(log.Lshortfile)

   demoMap := map[int]string{
      1: "xiaoming",
      2: "xiaoxiong",
   }
   value, ok := demoMap[3]
   if ok{
      log.Printf("value == %+v",value)
   }else{
      log.Println("no exsit")
   }

   var demoMap2 map[int]string    // nil
   demoMap2[1] = "hhh"     // panic: assignment to entry in nil map

}

对于访问访问一个 nil 的 map ,不会出现问题,但是如果是去写入数据到某个节点上,那么就会出现 panic: assignment to entry in nil map

Channel 通道零值 nil

对于通道 channel 零值 nil,我们就需要注意是从这个通道读取数据,还是将数据写入到这个通道中

从 nil 通道中读取数据

例如,若定义一个 channel ,var ch chan int

从 nil 通道中读取数据会阻塞: <- ch

写入数据到 nil 通道

写入数据到 nil 通道会阻塞: ch<-1

关闭一个 nil 通道,会 panic , panic: close of nil channel

当然,此处仅是聊聊关于 nil 涉及到的数据结构,以及简单的注意事项, 对于 channel 的原理和使用,可以查看文末的文章链接

指针零值 nil

对于指针的零值,我们应该是比较熟悉的了,如果你是从 C 语言转 Golang 的,那么这就更不在话下了

nil 的指针,异常点和前面说到的切片 slice 类似,当访问空指针上的值的时候,就会出现 panic

代码语言:javascript
复制
var ptr *int
log.Println(*ptr)
代码语言:javascript
复制
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x211f79]

自然,仍然和切片类似,对于 nil 的指针,我们可以正常打印指针自己的地址,以及直接打印这个指针指向的值

代码语言:javascript
复制
var ptr *int
log.Println(&ptr)
log.Println(ptr)

所以,一般在操作指针的时候,需要认真仔细,特别是对于新手使用指针,更要万分小心

但是一旦你熟悉了指针,你会爱上他的,由于 Golang 函数传参都是值传递

因此,我们一般开发的时候,会使用传指针的方式,虽然传递指针也是指针的拷贝,可是这样的资源开销会小很多

自然,对于指针和内存的内容,我们之后的文章再细聊

interface{} 零值 nil 和 函数零值 nil

函数零值 nil

对于函数的 nil,我们一般会使用在哪里呢?

例如我们传入的参数是一个函数的时候,具体的实现又需要这个函数去做业务,那么,这个时候我们传入了 nil,自然是会出问题的

代码语言:javascript
复制
func main() {
   testDemo(testFun)
   testDemo(nil)

}
func testFun(str string){
   fmt.Println("str == ",str)
}
func testDemo(fun func(string)){
   fun("hello")
}

所以,对于这种参数是函数的情况,咱们使用之前,需要去校验传入的函数是否是一个 nil,这已经是基本操作了

interface{} 零值 nil

interface{} 的零值,还记得占用多少字节吗?它占用的是 16 个字节

稍微了解 interface{} 的底层数据结构的就知道,他的底层是有一个 type 和一个data(无论是 iface 还是 eface),他俩都是指针,因此此处 nil 的 interface{} 占用 16 个字节

从此处,我们可以看到, interface{} 里面包含 2 个因素,只有当着俩因素都是 nil 的时候,整个 interface{} 才会是 nil

代码语言:javascript
复制
var in interface{}
var ptr  *int
if in == nil{
    // 此处的 in  类型是 nil ,值也是 nil ,因此会进来
}
in = ptr
if in == nil{
    // 此时的 in ,类型是 *int ,值是 nil ,因此不会进来
}

有没有觉得还是挺有趣的呢,本次文章仅讨论关于 nil 的内容,其他的延伸内容可以查看文末的地址

看到这里,有没有对 nil 有了更多的认知了呢?希望能够对你有帮助

文中提到的技术点,感兴趣的可以查看这些文章:

  • GO 中 slice 的实现原理
  • GO 中 map 的实现原理
  • 关于 interface{} 会有啥注意事项?上
  • 关于 interface{} 会有啥注意事项?下
  • GO通道和 sync 包的分享
  • 微服务线上问题排查困难?不知道问题出在哪一环?那是你还不会分布式链路追踪
  • k8s 服务升级为啥 pod 会部署到我们不期望的节点上??看来你还不懂污点和容忍度
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-09-28 23:40,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 阿兵云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • nil 和 true/false 一样,不是 Golang 的关键字
  • nil 占用的空间因不同的数据结构而不同
  • 切片零值 nil
  • map 零值 nil
  • Channel 通道零值 nil
  • 指针零值 nil
  • interface{} 零值 nil 和 函数零值 nil
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档