不得不知道的golang知识点之nil

qiangmzsx · · 7015 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

golang中的`nil`,很多人都误以为与Java、PHP等编程语言中的null一样。但是实际上Golang的niu复杂得多了,如果不信,那我们继续往下阅读。 `nil` 为预声明的标示符,定义在`builtin/builtin.go`, ``` // nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. // Type must be a pointer, channel, func, interface, map, or slice type var nil Type // Type is here for the purposes of documentation only. It is a stand-in // for any Go type, but represents the same type for any given function // invocation. type Type int ``` ## nil的零值 按照Go语言规范,任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针、函数、interface、slice、channel和map的零值都是nil。 **PS:这里没有说结构体struct的零值为nil,因为struct的零值与其属性有关** `nil`没有默认的类型,尽管它是多个类型的零值,必须显式或隐式指定每个nil用法的明确类型。 ``` package main func main() { // 明确. _ = (*struct{})(nil) _ = []int(nil) _ = map[int]bool(nil) _ = chan string(nil) _ = (func())(nil) _ = interface{}(nil) // 隐式. var _ *struct{} = nil var _ []int = nil var _ map[int]bool = nil var _ chan string = nil var _ func() = nil var _ interface{} = nil } ``` 如果关注过golang关键字的同学就会发现,里面并没有`nil`,也就是说`nil`并不是关键字,那么就可以在代码中定义`nil`,那么`nil`就会被隐藏。 ``` package main import "fmt" func main() { nil := 123 fmt.Println(nil) // 123 var _ map[string]int = nil //cannot use nil (type int) as type map[string]int in assignment } ``` ## nil类型的地址和值大小 `nil`类型的所有值的内存布局始终相同,换一句话说就是:不同类型`nil`的内存地址是一样的。 ``` package main import ( "fmt" ) func main() { var m map[int]string var ptr *int var sl []int fmt.Printf("%p\n", m) //0x0 fmt.Printf("%p\n", ptr ) //0x0 fmt.Printf("%p\n", sl ) //0x0 } ``` 业务中一般将`nil`值表示为异常。nil值的大小始终与其类型与`nil`值相同的`non-nil`值大小相同。因此, 表示不同零值的nil标识符可能具有不同的大小。 ``` package main import ( "fmt" "unsafe" ) func main() { var p *struct{} = nil fmt.Println( unsafe.Sizeof( p ) ) // 8 var s []int = nil fmt.Println( unsafe.Sizeof( s ) ) // 24 var m map[int]bool = nil fmt.Println( unsafe.Sizeof( m ) ) // 8 var c chan string = nil fmt.Println( unsafe.Sizeof( c ) ) // 8 var f func() = nil fmt.Println( unsafe.Sizeof( f ) ) // 8 var i interface{} = nil fmt.Println( unsafe.Sizeof( i ) ) // 16 } ``` 大小是编译器和体系结构所依赖的。以上打印结果为64位体系结构和正式 Go 编译器。对于32位体系结构, 打印的大小将是一半。 对于正式 Go 编译器, 同一种类的不同类型的两个nil值的大小始终相同。例如, 两个不同的切片类型 ( []int和[]string) 的两个nil值始终相同。 ## nil值比较 1.不同类型的`nil`是不能比较的。 ``` package main import ( "fmt" ) func main() { var m map[int]string var ptr *int fmt.Printf(m == ptr) //invalid operation: m == ptr (mismatched types map[int]string and *int) } ``` 在 Go 中, 两个不同可比较类型的两个值只能在一个值可以隐式转换为另一种类型的情况下进行比较。具体来说, 有两个案例两个不同的值可以比较: - 两个值之一的类型是另一个的基础类型。 - 两个值之一的类型实现了另一个值的类型 (必须是接口类型)。 **`nil`值比较并没有脱离上述规则。** ``` package main import ( "fmt" ) func main() { type IntPtr *int fmt.Println(IntPtr(nil) == (*int)(nil)) //true fmt.Println((interface{})(nil) == (*int)(nil)) //false } ``` 2.同一类型的两个`nil`值可能无法比较 因为golang中存在map、slice和函数类型是不可比较类型,它们有一个别称为`不可比拟的类型`,所以比较它们的`nil`亦是非法的。 ``` package main import ( "fmt" ) func main() { var v1 []int = nil var v2 []int = nil fmt.Println(v1 == v2) fmt.Println((map[string]int)(nil) == (map[string]int)(nil)) fmt.Println((func())(nil) == (func())(nil)) } ``` `不可比拟的类型`的值缺是可以与“纯nil”进行比较。 ``` package main import ( "fmt" ) func main() { fmt.Println((map[string]int)(nil) == nil) //true fmt.Println((func())(nil) == nil) //true } ``` 3.两`nil`值可能不相等 如果两个比较的nil值之一是一个接口值, 而另一个不是, 假设它们是可比较的, 则比较结果总是 false。原因是在进行比较之前, 接口值将转换为接口值的类型。转换后的接口值具有具体的动态类型, 但其他接口值没有。这就是为什么比较结果总是错误的。 ``` package main import ( "fmt" ) func main() { fmt.Println( (interface{})(nil) == (*int)(nil) ) // false } ``` ## 常见问题 1.函数返回 ``` func nilReturn() (string,error) { return nil,nil //cannot use nil as type string in return argument } ``` 因为`error`是接口类型所以`error`类型没有报错。 2.map的nil key map的key为指针、函数、interface、slice、channel和map,则key可以为nil。 ``` package main import ( "fmt" ) func main() { mmap := make(map[*string]int,4) a:="a" mmap[&a] = 1 mmap[nil] = 99 fmt.Println(mmap) //map[0xc042008220:1 <nil>:99] } ``` ## 总结 nil之所以比较难以理解因为我们经常混淆了nil值和nil类型,希望各位同学细细品味其中区别。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7015 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传