- Nil 是一个名词,表示“无”或者“零”。
- 在 Go 里,nil 是一个零值。
- 如果一个指针没有明确的指向,那么它的值就是 nil
- 除了指针,nil 还是 slice、map 和接口的零值。
- Go 语言的 nil,比以往语言中的 null 更为友好,并且用的没那么频繁,但是仍需谨慎使用。
nil 会导致 panic
- 如果指针没有明确的指向,那么程序将无法对其实施的解引用。
- 尝试解引用一个 nil 指针将导致程序崩溃。
package mainimport "fmt"func main() { var nowhere *int if nowhere != nil { fmt.Println(*nowhere) }}
保护你的方法
package mainimport "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 maintype person struct { age int}func (p *person) birthday() { if p == nil { return } p.age++}func main() { var nobody *person nobody.birthday()}
nil 函数值
package mainimport "fmt"func main() { var fn func(a, b int) int fmt.Println(fn == nil) // fn(1, 2)}
- 检查函数值是否为 nil,并在有需要时提供默认行为。
package mainimport ( "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport ( "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)}