- Nil 是一个名词,表示“无”或者“零”。
- 在 Go 里,nil 是一个零值。
- 如果一个指针没有明确的指向,那么它的值就是 nil
- 除了指针,nil 还是 slice、map 和接口的零值。
- Go 语言的 nil,比以往语言中的 null 更为友好,并且用的没那么频繁,但是仍需谨慎使用。
nil 会导致 panic
- 如果指针没有明确的指向,那么程序将无法对其实施的解引用。
- 尝试解引用一个 nil 指针将导致程序崩溃。
package main
import "fmt"
func main() {
var nowhere *int
if nowhere != nil {
fmt.Println(*nowhere)
}
}
保护你的方法
package main
import "fmt"
type person struct {
age int
}
func (p *person) birthday() {
p.age++
}
func main() {
var nobody *person
fmt.Println(nobody)
nobody.birthday()
}
- 因为值为 nil 的接收者和值为 nil 的参数在行为上并没有区别,所以 Go 语言即使在接收者为 nil 的情况下,也会继续调用方法。
package main
type person struct {
age int
}
func (p *person) birthday() {
if p == nil {
return
}
p.age++
}
func main() {
var nobody *person
nobody.birthday()
}
nil 函数值
package main
import "fmt"
func main() {
var fn func(a, b int) int
fmt.Println(fn == nil)
// fn(1, 2)
}
- 检查函数值是否为 nil,并在有需要时提供默认行为。
package main
import (
"fmt"
"sort"
)
func sortStrings(s []string, less func(i, j int) bool) {
if less == nil {
less = func(i, j int) bool { return s[i] < s[j] }
}
sort.Slice(s, less)
}
func main() {
food := []string{"onion", "carrot", "celery"}
sortStrings(food, nil)
fmt.Println(food)
}
nil slice
- 如果 slice 在声明之后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值就是 nil。
- 幸运的是,range、len、append 等内置函数都可以正常处理值为 nil 的 slice。
package main
import "fmt"
func main() {
var soup []string
fmt.Println(soup == nil)
for _, ingredient := range soup {
fmt.Println(ingredient)
}
fmt.Println(len(soup))
soup = append(soup, "onion", "carrot", "celery")
fmt.Println(soup)
t := make([]string, 0, 0)
fmt.Println(t == nil)
}
- 虽然空 slice 和值为 nil 的 slice 并不相等,但它们通常可以替换使用。
package main
import "fmt"
func main() {
soup := mirepoix(nil)
fmt.Println(soup)
}
func mirepoix(ingredients []string) []string {
return append(ingredients, "onion", "carrot", "celery")
}
nil map
- 和 slice 一样,如果 map 在声明后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值将会是默认的 nil
package main
import "fmt"
func main() {
var soup map[string]int
fmt.Println(soup == nil)
measurement, ok := soup["onion"]
if ok {
fmt.Println(measurement)
}
for ingredient, measurement := range soup {
fmt.Println(ingredient, measurement)
}
}
nil 接口
- 声明为接口类型的变量在未被赋值时,它的零值是 nil。
- 对于一个未被赋值的接口变量来说,它的接口类型和值都是 nil,并且变量本身也等于 nil。
- 当接口类型的变量被赋值后,接口就会在内部指向该变量的类型和值。
- 在 Go 中,接口类型的变量只有在类型和值都为 nil 时才等于 nil。
- 即使接口变量的值仍为 nil,但只要它的类型不是 nil,那么该变量就不等于 nil。
- 检验接口变量的内部表示
package main
import "fmt"
func main() {
var v interface{}
fmt.Printf("%T %v %v\n", v, v, v == nil)
var p *int
v = p
fmt.Printf("%T %v %v\n", v, v, v == nil)
fmt.Printf("%#v\n", v)
}
nil 之外的另一个选择
package main
import "fmt"
type number struct {
value int
valid bool
}
func newNumber(v int) number {
return number{value: v, valid: true}
}
func (n number) String() string {
if !n.valid {
return "not set"
}
return fmt.Sprintf("%d", n.value)
}
func main() {
n := newNumber(42)
fmt.Println(n)
e := number{}
fmt.Println(e)
}
作业题
- 亚瑟被一位骑士挡住了去路。正如 leftHand *item 变量的值为 nil 所示,这位英雄手上正空无一物。
- 请实现一个拥有 pickup(i _item) 和 give(to _character) 等方法的 character 结构,然后使用你在本节学到的知识编写一个脚本,使得亚瑟可以拿起一件物品并将其交给骑士,与此同时为每个动作打印出适当的描述。
package main
import (
"fmt"
)
type item struct {
name string
}
type character struct {
name string
leftHand *item
}
func (c *character) pickup(i *item) {
if c == nil || i == nil {
return
}
fmt.Printf("%v picks up a %v\n", c.name, i.name)
c.leftHand = i
}
func (c *character) give(to *character) {
if c == nil || to == nil {
return
}
if c.leftHand == nil {
fmt.Printf("%v has nothing to give\n", c.name)
return
}
if to.leftHand != nil {
fmt.Printf("%v's hands are full\n", to.name)
return
}
to.leftHand = c.leftHand
c.leftHand = nil
fmt.Printf("%v gives %v a %v\n", c.name, to.name, to.leftHand.name)
}
func (c character) String() string {
if c.leftHand == nil {
return fmt.Sprintf("%v is carrying nothing", c.name)
}
return fmt.Sprintf("%v is carrying a %v", c.name, c.leftHand.name)
}
func main() {
arthur := &character{name: "Arthur"}
shrubbery := &item{name: "shrubbery"}
arthur.pickup(shrubbery)
knight := &character{name: "Knight"}
arthur.give(knight)
fmt.Println(arthur)
fmt.Println(knight)
}