前言
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 // 使用了无类型的nilnil是一个预先标示好的表示指针,通道,函数,接口,字典或者切片类型的0值。
零值们 (他们都意味着什么?)
在golang中的nil种类
pointers slices maps channels functions interfaces
指针
它们指向内存地址
就像c c++, 但是go也有不同的地方:
- 没有指针运算, 所以内存是安全的。
- 带有gc
空指针
- 指向nil,亦可以说是什么都没有
- 零值指针
切片的内部
[]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通道可以失能一个selectnil 函数
函数类型是一类公民在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,而是拥抱它!