2018年11月6日
作为一名长期从事 java 开发的人,我痴迷于 null 值的检查和处理。可在 Golang 中情况有些不同。在这篇文章中,我将描述一下 nil 和 零值 在 golang 中的用法。
Non-nillable(非空) 和 nillable(空) 类型
在 go 中类型可以是 空 或 非空 。非空类型永远不能是 nil,也永远不会引起 nil-panic(等价于 java 的 nullpointerexception),虽然 go 不像在 java(或其他具有空类型的语言)中有那样,但是在处理空类型时,我们必须小心一点。
Non-nillable 基本类型
在 go 中,基本类型不能为空。像这样:
var a int = nil
无法编译。因为 int 永远不能为 nil。未赋值 int 类型的默认值为 0。运行语句
var a int // default value of int, cannot be nil
fmt.Println(a) // 0
将输出 int 的默认值 0,我们称之为类型的零值。
与 int 默认为 0 的方式相同,下面这些是其他基本类型及其零值:
| 基本类型 | 零值 |
|---|---|
| int, int8, int16, int32, int64 | 0 |
| uint, uint8, uint16, uint32, uint64 | 0 |
| uintptr | 0 |
| float32, float64 | 0.0 |
| byte | 0 |
| rune | 0 |
| string | "” (empty string) |
| complex64, complex128 | (0,0i) |
| arrays of non-nillable types | array of zero-values |
| arrays of nillable types | array of nil-values |
Non-nillable 结构体
组合的 struct 类型也是不可为空的,struct 的默认值将包含其所有字段的默认值。
分析 struct 类型 Person 的代码,
type Person struct {
Name string
Age int
}
var p Person // zero-value of type Person
fmt.Printf("[%#v]\n", p)
当在 main.go 运行这段代码,会打印 [main.Person{Name:"", Age:0}] 。你可以在 golayground 上测试 这段代码。
Nillable 类型
更高级的类型是 nillable ,如果它们没有初始化,可能会导致异常。
nillable 类型包括 函数(function)、通道(channel)、切片(slice)、map、接口类型(interface)和指针(pointer)。
但是,nil-slice 和 nil-map 仍然可以使用,在使用它们之前不必进行初始化。
nil-maps
如果 map 值为 nil,map 将始终返回该值的零值,这与返回 map 中不存在的 key 时代码一样。代码如下:
var p map[int]string // nil map
fmt.Printf(" %#v length %d \n", p[99], len(p))
只需打印 "" length 0 ,键为99的字符串就是 string 值为零。
但是,将值赋给 nil-map ,会异常:
var p map[string]int // nil map
p["nils"] = 19 // panic: assignment to entry in nil map
nil-slice
在外部引用切片 will 会导致异常,但是像 len() 和 cap() 这样的操作不会导致异常。它们只返回 0,因为对于未初始化的切片,容量和长度都为零。可以在 nil-slice 上安全地调用 Append 。代码如下:
var p []string // nil slice
fmt.Printf("uninitialized -> %d, %d\n", len(p), cap(p))
p1 := append(p, "nils") // create a new slice p1 from p
fmt.Printf("after append -> %d, %d %#v\n", len(p1), cap(p1), p1)
打印:
uninitialized -> 0, 0
after append -> 1, 1 []string{"nils"}
可为 nil 的指针,函数和接口会导致异常
然而,指针和接口类型是 nillable 的。在处理这些类型的问题时,我们必须考虑它们是否为零,以避免异常。例如,这些代码片段不行:
var p *int // pointer to an int
*p++ // panic: runtime error: invalid memory address or nil pointer dereference
// p is an address to nothing, and therefore is nil
var p error // nil-value of type error
error.Error() // panic: runtime error: invalid memory address or nil pointer dereference
var f func(string) // nil-function
f("oh oh") // panic: runtime error: invalid memory address or nil pointer dereference
nil-channel 会永远阻塞
尝试从 nil-channel 读取或写入 nil-channel 将永远阻塞。关闭 nil-channel 会导致异常。
Wrapup
nil 在 go 中有很好的定义。了解什么可以是 nil 以及如何处理不同类型的 nil 值可以增加你对发生的事情的理解,并可以帮助你编写更好的 go 代码。