• Nil 是一个名词,表示“无”或者“零”。
  • 在 Go 里,nil 是一个零值。
  • 如果一个指针没有明确的指向,那么它的值就是 nil
  • 除了指针,nil 还是 slice、map 和接口的零值。
  • Go 语言的 nil,比以往语言中的 null 更为友好,并且用的没那么频繁,但是仍需谨慎使用。

nil 会导致 panic

  • 如果指针没有明确的指向,那么程序将无法对其实施的解引用。
  • 尝试解引用一个 nil 指针将导致程序崩溃。
  1. package main
  2. import "fmt"
  3. func main() {
  4. var nowhere *int
  5. if nowhere != nil {
  6. fmt.Println(*nowhere)
  7. }
  8. }

保护你的方法

  • 避免 nil 引发 panic
  1. package main
  2. import "fmt"
  3. type person struct {
  4. age int
  5. }
  6. func (p *person) birthday() {
  7. p.age++
  8. }
  9. func main() {
  10. var nobody *person
  11. fmt.Println(nobody)
  12. nobody.birthday()
  13. }
  • 因为值为 nil 的接收者和值为 nil 的参数在行为上并没有区别,所以 Go 语言即使在接收者为 nil 的情况下,也会继续调用方法。
  1. package main
  2. type person struct {
  3. age int
  4. }
  5. func (p *person) birthday() {
  6. if p == nil {
  7. return
  8. }
  9. p.age++
  10. }
  11. func main() {
  12. var nobody *person
  13. nobody.birthday()
  14. }

nil 函数值

  • 当变量被声明为函数类型时,它的默认值是 nil。
  1. package main
  2. import "fmt"
  3. func main() {
  4. var fn func(a, b int) int
  5. fmt.Println(fn == nil)
  6. // fn(1, 2)
  7. }
  • 检查函数值是否为 nil,并在有需要时提供默认行为。
  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func sortStrings(s []string, less func(i, j int) bool) {
  7. if less == nil {
  8. less = func(i, j int) bool { return s[i] < s[j] }
  9. }
  10. sort.Slice(s, less)
  11. }
  12. func main() {
  13. food := []string{"onion", "carrot", "celery"}
  14. sortStrings(food, nil)
  15. fmt.Println(food)
  16. }

nil slice

  • 如果 slice 在声明之后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值就是 nil。
  • 幸运的是,range、len、append 等内置函数都可以正常处理值为 nil 的 slice。
  1. package main
  2. import "fmt"
  3. func main() {
  4. var soup []string
  5. fmt.Println(soup == nil)
  6. for _, ingredient := range soup {
  7. fmt.Println(ingredient)
  8. }
  9. fmt.Println(len(soup))
  10. soup = append(soup, "onion", "carrot", "celery")
  11. fmt.Println(soup)
  12. t := make([]string, 0, 0)
  13. fmt.Println(t == nil)
  14. }
  • 虽然空 slice 和值为 nil 的 slice 并不相等,但它们通常可以替换使用。
  1. package main
  2. import "fmt"
  3. func main() {
  4. soup := mirepoix(nil)
  5. fmt.Println(soup)
  6. }
  7. func mirepoix(ingredients []string) []string {
  8. return append(ingredients, "onion", "carrot", "celery")
  9. }

nil map

  • 和 slice 一样,如果 map 在声明后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值将会是默认的 nil
  1. package main
  2. import "fmt"
  3. func main() {
  4. var soup map[string]int
  5. fmt.Println(soup == nil)
  6. measurement, ok := soup["onion"]
  7. if ok {
  8. fmt.Println(measurement)
  9. }
  10. for ingredient, measurement := range soup {
  11. fmt.Println(ingredient, measurement)
  12. }
  13. }

nil 接口

  • 声明为接口类型的变量在未被赋值时,它的零值是 nil。
  • 对于一个未被赋值的接口变量来说,它的接口类型和值都是 nil,并且变量本身也等于 nil。
  • 当接口类型的变量被赋值后,接口就会在内部指向该变量的类型和值。
  • 在 Go 中,接口类型的变量只有在类型和值都为 nil 时才等于 nil。
    • 即使接口变量的值仍为 nil,但只要它的类型不是 nil,那么该变量就不等于 nil。
  • 检验接口变量的内部表示
  1. package main
  2. import "fmt"
  3. func main() {
  4. var v interface{}
  5. fmt.Printf("%T %v %v\n", v, v, v == nil)
  6. var p *int
  7. v = p
  8. fmt.Printf("%T %v %v\n", v, v, v == nil)
  9. fmt.Printf("%#v\n", v)
  10. }

nil 之外的另一个选择

  1. package main
  2. import "fmt"
  3. type number struct {
  4. value int
  5. valid bool
  6. }
  7. func newNumber(v int) number {
  8. return number{value: v, valid: true}
  9. }
  10. func (n number) String() string {
  11. if !n.valid {
  12. return "not set"
  13. }
  14. return fmt.Sprintf("%d", n.value)
  15. }
  16. func main() {
  17. n := newNumber(42)
  18. fmt.Println(n)
  19. e := number{}
  20. fmt.Println(e)
  21. }

作业题

  • 亚瑟被一位骑士挡住了去路。正如 leftHand *item 变量的值为 nil 所示,这位英雄手上正空无一物。
  • 请实现一个拥有 pickup(i _item) 和 give(to _character) 等方法的 character 结构,然后使用你在本节学到的知识编写一个脚本,使得亚瑟可以拿起一件物品并将其交给骑士,与此同时为每个动作打印出适当的描述。
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type item struct {
  6. name string
  7. }
  8. type character struct {
  9. name string
  10. leftHand *item
  11. }
  12. func (c *character) pickup(i *item) {
  13. if c == nil || i == nil {
  14. return
  15. }
  16. fmt.Printf("%v picks up a %v\n", c.name, i.name)
  17. c.leftHand = i
  18. }
  19. func (c *character) give(to *character) {
  20. if c == nil || to == nil {
  21. return
  22. }
  23. if c.leftHand == nil {
  24. fmt.Printf("%v has nothing to give\n", c.name)
  25. return
  26. }
  27. if to.leftHand != nil {
  28. fmt.Printf("%v's hands are full\n", to.name)
  29. return
  30. }
  31. to.leftHand = c.leftHand
  32. c.leftHand = nil
  33. fmt.Printf("%v gives %v a %v\n", c.name, to.name, to.leftHand.name)
  34. }
  35. func (c character) String() string {
  36. if c.leftHand == nil {
  37. return fmt.Sprintf("%v is carrying nothing", c.name)
  38. }
  39. return fmt.Sprintf("%v is carrying a %v", c.name, c.leftHand.name)
  40. }
  41. func main() {
  42. arthur := &character{name: "Arthur"}
  43. shrubbery := &item{name: "shrubbery"}
  44. arthur.pickup(shrubbery)
  45. knight := &character{name: "Knight"}
  46. arthur.give(knight)
  47. fmt.Println(arthur)
  48. fmt.Println(knight)
  49. }