前言

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的具体表现

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

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

零值们(他们是啥?)
    bool       --> false
    numbers    --> 0
    string     --> ""
    pointers   --> nil
    slices     --> nil
    maps       --> nil
    channels   --> nil
    functions  --> nil
    interfaces --> nil
结构体中的0值
    type Person struct {
        AgeYears int
        Name     string
        Friend   []Person
    }
    var p Person //Person{o,"",nil}

nil类型

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

无类型的0

    a := false // 布尔
    a := " "   // 字符串
    a := 0     // 整形
    a := 0.0   // 浮点型
    a := nil   // 使用了无类型的nil
nil是一个预先标示好的表示指针,通道,函数,接口,字典或者切片类型的0值。

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

在golang中的nil种类

    pointers
    slices
    maps
    channels
    functions
    interfaces

指针

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

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

空指针

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

切片的内部

    []byte
    ptr *elem
    len 0
    cap 0
    s := make([]byte,5)
    ptr指向一个底层数组它有五个元素[0,0,0,0,0]
空slice
    []byte
    ptr nil
    len 0
    cap 0
    var s []byte
通道,字典,还有函数
    ptr *something 内部指向一个指针
    ptr --> implementtation(指针指向了一些实现)
空通道,字典,还有函数
    ptr  nil
接口
    一个借口存储了类型和值
    (type, value)
    var s fmt.Stringer    // Stringer(nil, nil)
    fmt.Println(s == nil) // true
    结论:(nil, nil)等于nil
    另外一个例子:
    var p *Person          // *Person是空的
    var s fmt.Stringer = p // Stringer(*Person, nil)
    fmt.Println(s == nil)  // false
    另外一个结论:
    (*Person, nil)它不等于nil
什么时候nil不是nil?
    func do() error {    // 错误类型(*doErrror, nil)
        var err *doError 
        return err        // 类型*doError是空的
    }
    func main() {
        err := do()      // 错误类型(*doErrror, nil)
        fmt.Println( err== nil )  //false
    }
    结论:不要去定义确切的错误类型
    不然你熟悉的就可能有问题了
    if err != nil {
    }
    func do() *doError{        // 空的*doError类型
        return nil
    }
    func main() {
        err := do()             // 空的*doError类型
        fmt.Println(err == nil) // true
    }
    func do() *doError {       // 空的*doError类型
        return  nil
    }
    func wrapDo() error {      // error(*doError, nil)
        return do()            // 空的*doError类型
    }
    func main() {
        err := wrapDo()         // error(*doError, nil)
        fmt.Println(err == nil) // 显然fasle,如果你讨厌显然这个词,就往上稍微看一下
    }
    结论:不要返回确切的错误类型
    nil类型的含义
    pointers     指向什么都没有
    slices     没有底层数组
    maps         没有初始化
    channels     没有初始化
    functions    没有初始化
    interfaces   没有赋值,即使是一个空指针

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

以下这些如何变得有用

    pointers
    slices
    maps
    channels
    functions
    interfaces
指针
    var p *int
    p == nil    //true
    *p          //panic : invalid memory address or nil pointer dereference
    type person struct{}
    func sayHi(p *persion)    {fmt.Println("hi")}
    func (p *persion) sayHi() {fmt.Println("hi")}
    var p *person
    p.sayHi()         //hi
    空接受者是有用的
    func(t *tree) Find(v int) bool {
        if t == nil {
            return false
        }
    }
    func(t *tree) Find(v int) int {
        if t == nil {
            return 0
        }
    }
    func(t *tree) Find(v int) string {
        if t == nil {
            return ""
        }
    }
切片
    var s []slice
    len(s)        // 0
    cap(s)        // 0
    for range s   // 迭代0值
    s[i]          // panic: index out of range
    追加到空的nil切片
    var s []int
    for i:=0; i <10; i++ {
        s = append(s,i)
    }
    注意这里会产生底层数组的扩张
    建议: 可以使用nil切片,它们多数的时候都是足够快的。
空字典
    var m map[t]u
    len(m)          // 0
    for range m     // 迭代0值
    v, ok := m[i]   // 零值,false
    m[i] = x        // panic: assignment to entry in nil map (指定进入到一个空m字典中)
    func NewGet(url string, headers map[string]string (http.*Request,error) {
        req, err = http.NewRequest(http.MenthodGet,url,nil)
        if err != nil {
            return nil, err
        }
        for k, v := range headers {
            req.Header.Set(k,v)
        }
       return  req, nil
    }
    NewGet(
    "http://google.com",
    map[string]string{
        "USER_AGENT": "golang/gopher"
    }
    )
    response:
    GET /HTTP/1.1
    Host: google.com
    User_agent: google/gopher
    使用空的字典
    NewGet(
    "http://google.com",
    map[string]string{}
    )
    response:
    GET /HTTP/1.1
    Host: google.com
    nil也是有效的空字典
    NewGet("http://google.com", nil)
    response:
    GET /HTTP/1.1
    Host: google.com
    使用nil字典做为一个只读的空字典
nil通道
    var  c chan t 
    <-c           // 永远阻塞
    c <-x         // 永远阻塞
    close(c)      // panic:  关闭一个nil通道
    关闭的通道
    var  c chan t 
    v, ok<-c      // 零值, false
    c <-x         //panic: 发送数据到一个关闭的通道
    close(c)      // panic:  关闭一个nil通道
    关闭一个通道
    case v, ok := <- a:
    if !ok {
        a = nil 
        fmt.Println("a is now closed")
    }
    使用一个nil通道可以失能一个select
nil 函数
函数类型是一类公民在go中
函数也可以做为结构体的字段
它们需要一个零值,逻辑上可以叫做nil
    type Foo struct {
        f func() error
     }
    nil函数做为默认值
    懒惰的初始化变量
    同时也暗示着默认的行为
    func NewServer(logger func(string, ...interface{}){
        if logger == nil {
            //实现我们说的默认行为
            logger = log.Printf
        }
        logger("initializing %s", os.Getenv("hostname"))
        ...
    }
    )
接口
通常nil接口被用作一个信号量
    if err != nil {
        // task
    }
为什么*person不等于nil空口
    summer
    type Summer interface {
        func sum() int 
    }
    注意: t要实现sum()方法才能赋值给s接口类型
    vat t *tree
    var s Summer = t
    fmt.Println(t == nil, s.Sum()) // true, 0
    具体实现:
    type ints []int
    func (i ints) sum() int {
        s := 0
        for _,v  := range i {
            s += v
        }
        return s
    }
    var i ints
    var s Summer = i 
    fmt.Println(i == nil, s.Sum()) // true, 0
    说明了: nil值是可以实现接口的
    nil值和默认值 
    func doSum(s Summer) int {
        if s == nil {
           return 0
        }
        return s.sum()
    }
    var t *tree
    doSum(t)       // (*tree, nil)
    var i ints 
    doSum(i)      // (ints, nil)
    doSum(nil)    // (nil, nil)
    Summer 接口的变化
    建议使用nil接口去通知默认事件
nil是有用的
    pointers   //方法可以在一个nil指针上调用
    slices     //有效的0值
    maps       //只读
    channels   //本质是一个并发模型
    functions  //需要实现
    interfaces //最常见的是做为go中的信号
最后

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