@aliasliyu4
2017-01-01T06:29:38.000000Z
字数 4368
阅读 5137
nil是什么?
nil在go中又是什么?
nil意味着什么?
nil有用吗?
我们常常会把nil拼写成null,学过c的同学肯定听过这个null符号,甚至某些让人痛恨的同学还故意取一个这样的名字。
nil nothing, 特别的指定一场比赛中得分为0
null 没有,空
none 古英语,一个没有
zero null duck nada zip naught
aught cipher love nil zilch
the letter 'O' nought ought
快速排序的发明者car hoare说:我把它叫做上亿美金的错误。它是这个在1965年发明的空引用。在那个时候,我正在设计第一个综合的类型系统以便在面向对象的语言中引用。也就是说那个时候又了null的概念。
这个错误大家在go经常碰到panic: runtime error: invalid memory address or nil pointer dereference恐慌:运行时错误: 无效的内存地址或者空指针被引用类似的js也有这样的错误提示:Uncaught TypeErrorundefined is not a function
总结上面的错误代码: nil是会引起恐慌的,接下来就不敢想象了。
并且各种语言都有自己的错误抛出方式,nil就是go的方法。
bool --> falsenumbers --> 0string --> ""pointers --> nilslices --> nilmaps --> nilchannels --> nilfunctions --> nilinterfaces --> nil
type Person struct {AgeYears intName stringFriend []Person}var p Person //Person{o,"",nil}
除非这个值是预先标示为nil否则它就没有类型
a := false // 布尔a := " " // 字符串a := 0 // 整形a := 0.0 // 浮点型a := nil // 使用了无类型的nil
nil是一个预先标示好的表示指针,通道,函数,接口,字典或者切片类型的0值。
在golang中的nil种类
pointersslicesmapschannelsfunctionsinterfaces
它们指向内存地址
就像c c++, 但是go也有不同的地方:
空指针
[]byteptr *elemlen 0cap 0s := make([]byte,5)ptr指向一个底层数组它有五个元素[0,0,0,0,0]
空slice
[]byteptr nillen 0cap 0var 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
func do() error { // 错误类型(*doErrror, nil)var err *doErrorreturn 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 没有赋值,即使是一个空指针
以下这些如何变得有用
pointersslicesmapschannelsfunctionsinterfaces
指针
var p *intp == nil //true*p //panic : invalid memory address or nil pointer dereferencetype person struct{}func sayHi(p *persion) {fmt.Println("hi")}func (p *persion) sayHi() {fmt.Println("hi")}var p *personp.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 []slicelen(s) // 0cap(s) // 0for range s // 迭代0值s[i] // panic: index out of range追加到空的nil切片var s []intfor i:=0; i <10; i++ {s = append(s,i)}注意这里会产生底层数组的扩张建议: 可以使用nil切片,它们多数的时候都是足够快的。
空字典
var m map[t]ulen(m) // 0for range m // 迭代0值v, ok := m[i] // 零值,falsem[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.1Host: google.comUser_agent: google/gopher使用空的字典NewGet("http://google.com",map[string]string{})response:GET /HTTP/1.1Host: google.comnil也是有效的空字典NewGet("http://google.com", nil)response:GET /HTTP/1.1Host: google.com使用nil字典做为一个只读的空字典
nil通道
var c chan t<-c // 永远阻塞c <-x // 永远阻塞close(c) // panic: 关闭一个nil通道关闭的通道var c chan tv, ok<-c // 零值, falsec <-x //panic: 发送数据到一个关闭的通道close(c) // panic: 关闭一个nil通道关闭一个通道case v, ok := <- a:if !ok {a = nilfmt.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}
summertype Summer interface {func sum() int}注意: t要实现sum()方法才能赋值给s接口类型vat t *treevar s Summer = tfmt.Println(t == nil, s.Sum()) // true, 0具体实现:type ints []intfunc (i ints) sum() int {s := 0for _,v := range i {s += v}return s}var i intsvar s Summer = ifmt.Println(i == nil, s.Sum()) // true, 0说明了: nil值是可以实现接口的nil值和默认值func doSum(s Summer) int {if s == nil {return 0}return s.sum()}var t *treedoSum(t) // (*tree, nil)var i intsdoSum(i) // (ints, nil)doSum(nil) // (nil, nil)Summer 接口的变化建议使用nil接口去通知默认事件
pointers //方法可以在一个nil指针上调用slices //有效的0值maps //只读channels //本质是一个并发模型functions //需要实现interfaces //最常见的是做为go中的信号
让我们不再去逃避nil,而是拥抱它!