[关闭]
@aliasliyu4 2017-01-01T14:29:38.000000Z 字数 4368 阅读 4864

(译)理解nil--golang

前言

nil是什么?

nil在go中又是什么?

nil意味着什么?

nil有用吗?

我们常常会把nil拼写成null,学过c的同学肯定听过这个null符号,甚至某些让人痛恨的同学还故意取一个这样的名字。

词源

nil nothing, 特别的指定一场比赛中得分为0

null 没有,空

none 古英语,一个没有

数字0在英文中单词的表示

zero null duck nada zip naught

aught cipher love nil zilch

the letter 'O' nought ought

一点小历史

快速排序的发明者car hoare说:我把它叫做上亿美金的错误。它是这个在1965年发明的空引用。在那个时候,我正在设计第一个综合的类型系统以便在面向对象的语言中引用。也就是说那个时候又了null的概念。

引出nil的具体表现

  1. 这个错误大家在go经常碰到
  2. panic: runtime error: invalid memory address or nil pointer dereference
  3. 恐慌:运行时错误: 无效的内存地址或者空指针被引用
  4. 类似的js也有这样的错误提示:
  5. Uncaught TypeError
  6. undefined is not a function

总结上面的错误代码: nil是会引起恐慌的,接下来就不敢想象了。
并且各种语言都有自己的错误抛出方式,nil就是go的方法。

零值们(他们是啥?)

  1. bool --> false
  2. numbers --> 0
  3. string --> ""
  4. pointers --> nil
  5. slices --> nil
  6. maps --> nil
  7. channels --> nil
  8. functions --> nil
  9. interfaces --> nil

结构体中的0值

  1. type Person struct {
  2. AgeYears int
  3. Name string
  4. Friend []Person
  5. }
  6. var p Person //Person{o,"",nil}

nil类型

除非这个值是预先标示为nil否则它就没有类型

无类型的0

  1. a := false // 布尔
  2. a := " " // 字符串
  3. a := 0 // 整形
  4. a := 0.0 // 浮点型
  5. a := nil // 使用了无类型的nil

nil是一个预先标示好的表示指针,通道,函数,接口,字典或者切片类型的0值。

零值们 (他们都意味着什么?)

在golang中的nil种类

  1. pointers
  2. slices
  3. maps
  4. channels
  5. functions
  6. interfaces

指针

它们指向内存地址
就像c c++, 但是go也有不同的地方:

  1. 没有指针运算, 所以内存是安全的。
  2. 带有gc

空指针

  1. 指向nil,亦可以说是什么都没有
  2. 零值指针

切片的内部

  1. []byte
  2. ptr *elem
  3. len 0
  4. cap 0
  5. s := make([]byte,5)
  6. ptr指向一个底层数组它有五个元素[0,0,0,0,0]

空slice

  1. []byte
  2. ptr nil
  3. len 0
  4. cap 0
  5. var s []byte

通道,字典,还有函数

  1. ptr *something 内部指向一个指针
  2. ptr --> implementtation(指针指向了一些实现)

空通道,字典,还有函数

  1. ptr nil

接口

  1. 一个借口存储了类型和值
  2. (type, value)
  3. var s fmt.Stringer // Stringer(nil, nil)
  4. fmt.Println(s == nil) // true
  5. 结论:(nil, nil)等于nil
  6. 另外一个例子:
  7. var p *Person // *Person是空的
  8. var s fmt.Stringer = p // Stringer(*Person, nil)
  9. fmt.Println(s == nil) // false
  10. 另外一个结论:
  11. (*Person, nil)它不等于nil

什么时候nil不是nil?

  1. func do() error { // 错误类型(*doErrror, nil)
  2. var err *doError
  3. return err // 类型*doError是空的
  4. }
  5. func main() {
  6. err := do() // 错误类型(*doErrror, nil)
  7. fmt.Println( err== nil ) //false
  8. }
  9. 结论:不要去定义确切的错误类型
  10. 不然你熟悉的就可能有问题了
  11. if err != nil {
  12. }
  13. func do() *doError{ // 空的*doError类型
  14. return nil
  15. }
  16. func main() {
  17. err := do() // 空的*doError类型
  18. fmt.Println(err == nil) // true
  19. }
  20. func do() *doError { // 空的*doError类型
  21. return nil
  22. }
  23. func wrapDo() error { // error(*doError, nil)
  24. return do() // 空的*doError类型
  25. }
  26. func main() {
  27. err := wrapDo() // error(*doError, nil)
  28. fmt.Println(err == nil) // 显然fasle,如果你讨厌显然这个词,就往上稍微看一下
  29. }
  30. 结论:不要返回确切的错误类型
  31. nil类型的含义
  32. pointers 指向什么都没有
  33. slices 没有底层数组
  34. maps 没有初始化
  35. channels 没有初始化
  36. functions 没有初始化
  37. interfaces 没有赋值,即使是一个空指针

使得0值变得有用 -- Rob Pike

以下这些如何变得有用

  1. pointers
  2. slices
  3. maps
  4. channels
  5. functions
  6. interfaces

指针

  1. var p *int
  2. p == nil //true
  3. *p //panic : invalid memory address or nil pointer dereference
  4. type person struct{}
  5. func sayHi(p *persion) {fmt.Println("hi")}
  6. func (p *persion) sayHi() {fmt.Println("hi")}
  7. var p *person
  8. p.sayHi() //hi
  9. 空接受者是有用的
  10. func(t *tree) Find(v int) bool {
  11. if t == nil {
  12. return false
  13. }
  14. }
  15. func(t *tree) Find(v int) int {
  16. if t == nil {
  17. return 0
  18. }
  19. }
  20. func(t *tree) Find(v int) string {
  21. if t == nil {
  22. return ""
  23. }
  24. }

切片

  1. var s []slice
  2. len(s) // 0
  3. cap(s) // 0
  4. for range s // 迭代0值
  5. s[i] // panic: index out of range
  6. 追加到空的nil切片
  7. var s []int
  8. for i:=0; i <10; i++ {
  9. s = append(s,i)
  10. }
  11. 注意这里会产生底层数组的扩张
  12. 建议: 可以使用nil切片,它们多数的时候都是足够快的。

空字典

  1. var m map[t]u
  2. len(m) // 0
  3. for range m // 迭代0值
  4. v, ok := m[i] // 零值,false
  5. m[i] = x // panic: assignment to entry in nil map (指定进入到一个空m字典中)
  6. func NewGet(url string, headers map[string]string (http.*Request,error) {
  7. req, err = http.NewRequest(http.MenthodGet,url,nil)
  8. if err != nil {
  9. return nil, err
  10. }
  11. for k, v := range headers {
  12. req.Header.Set(k,v)
  13. }
  14. return req, nil
  15. }
  16. NewGet(
  17. "http://google.com",
  18. map[string]string{
  19. "USER_AGENT": "golang/gopher"
  20. }
  21. )
  22. response:
  23. GET /HTTP/1.1
  24. Host: google.com
  25. User_agent: google/gopher
  26. 使用空的字典
  27. NewGet(
  28. "http://google.com",
  29. map[string]string{}
  30. )
  31. response:
  32. GET /HTTP/1.1
  33. Host: google.com
  34. nil也是有效的空字典
  35. NewGet("http://google.com", nil)
  36. response:
  37. GET /HTTP/1.1
  38. Host: google.com
  39. 使用nil字典做为一个只读的空字典

nil通道

  1. var c chan t
  2. <-c // 永远阻塞
  3. c <-x // 永远阻塞
  4. close(c) // panic: 关闭一个nil通道
  5. 关闭的通道
  6. var c chan t
  7. v, ok<-c // 零值, false
  8. c <-x //panic: 发送数据到一个关闭的通道
  9. close(c) // panic: 关闭一个nil通道
  10. 关闭一个通道
  11. case v, ok := <- a:
  12. if !ok {
  13. a = nil
  14. fmt.Println("a is now closed")
  15. }
  16. 使用一个nil通道可以失能一个select

nil 函数
函数类型是一类公民在go中
函数也可以做为结构体的字段
它们需要一个零值,逻辑上可以叫做nil

  1. type Foo struct {
  2. f func() error
  3. }
  4. nil函数做为默认值
  5. 懒惰的初始化变量
  6. 同时也暗示着默认的行为
  7. func NewServer(logger func(string, ...interface{}){
  8. if logger == nil {
  9. //实现我们说的默认行为
  10. logger = log.Printf
  11. }
  12. logger("initializing %s", os.Getenv("hostname"))
  13. ...
  14. }
  15. )

接口
通常nil接口被用作一个信号量

  1. if err != nil {
  2. // task
  3. }

为什么*person不等于nil空口

  1. summer
  2. type Summer interface {
  3. func sum() int
  4. }
  5. 注意: t要实现sum()方法才能赋值给s接口类型
  6. vat t *tree
  7. var s Summer = t
  8. fmt.Println(t == nil, s.Sum()) // true, 0
  9. 具体实现:
  10. type ints []int
  11. func (i ints) sum() int {
  12. s := 0
  13. for _,v := range i {
  14. s += v
  15. }
  16. return s
  17. }
  18. var i ints
  19. var s Summer = i
  20. fmt.Println(i == nil, s.Sum()) // true, 0
  21. 说明了: nil值是可以实现接口的
  22. nil值和默认值
  23. func doSum(s Summer) int {
  24. if s == nil {
  25. return 0
  26. }
  27. return s.sum()
  28. }
  29. var t *tree
  30. doSum(t) // (*tree, nil)
  31. var i ints
  32. doSum(i) // (ints, nil)
  33. doSum(nil) // (nil, nil)
  34. Summer 接口的变化
  35. 建议使用nil接口去通知默认事件

nil是有用的

  1. pointers //方法可以在一个nil指针上调用
  2. slices //有效的0值
  3. maps //只读
  4. channels //本质是一个并发模型
  5. functions //需要实现
  6. interfaces //最常见的是做为go中的信号

最后

让我们不再去逃避nil,而是拥抱它!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注