[译] 理解 nil(Golang)

(译)理解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的具体表现

这个错误大家在go经常碰到
panic:runtime error:invalid memory addressor nilpointer dereference
恐慌:运行时错误: 无效的内存地址或者空指针被引用

类似的js也有这样的错误提示:
Uncaught TypeError
undefined is notafunction

总结上面的错误代码: nil是会引起恐慌的,接下来就不敢想象了。

并且各种语言都有自己的错误抛出方式,nil就是go的方法。

零值们(他们是啥?)

bool --> false
numbers--> 0
string --> ""
pointers--> nil
slices--> nil
maps--> nil
channels--> nil
functions--> nil
interfaces--> nil

结构体中的0值

typePerson struct {
 AgeYears int
 Name string
 Friend []Person
}

varpPerson //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
len0
cap0

s:=make([]byte,5)

ptr指向一个底层数组它有五个元素[0,0,0,0,0]

空slice

[]byte
ptrnil
len0
cap0

vars[]byte

通道,字典,还有函数

ptr*something内部指向一个指针

ptr-->implementtation(指针指向了一些实现)

空通道,字典,还有函数

  1. ptr nil

接口

一个借口存储了类型和值
(type,value)

vars fmt.Stringer // Stringer(nil, nil)
fmt.Println(s== nil) // true

结论:(nil, nil)等于nil

另外一个例子:

varp*Person // *Person是空的
vars fmt.Stringer =p// Stringer(*Person, nil)
fmt.Println(s== nil) // false

另外一个结论:
(*Person, nil)它不等于nil

什么时候nil不是nil?

funcdo()error{ // 错误类型(*doErrror, nil)
 varerr*doError
 returnerr// 类型*doError是空的
}

func main() {
err:= do() // 错误类型(*doErrror, nil)
fmt.Println(err== nil ) //false
}

结论:不要去定义确切的错误类型
不然你熟悉的就可能有问题了
iferr!= nil {
}


funcdo() *doError{ // 空的*doError类型
 return nil
}

func main() {
err:= do() // 空的*doError类型
fmt.Println(err== nil) // true
}


funcdo() *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

指针

varp*int
p== nil //true
*p//panic : invalid memory address or nil pointer dereference

type personstruct{}
func sayHi(p*persion) {fmt.Println("hi")}
func(p*persion)sayHi() {fmt.Println("hi")}
varp*person
p.sayHi() //hi

空接受者是有用的

func(t*tree) Find(vint) bool {
 ift== nil {
 return false
 }
}

func(t*tree) Find(vint) int {
 ift== nil {
 return 0
 }
}

func(t*tree) Find(vint) string {
 ift== nil {
 return ""
 }
}

切片

vars[]slice
len(s) // 0
cap(s) // 0
forrange s// 迭代0值
s[i] // panic: index out of range

追加到空的nil切片
vars[]int
fori:=0;i<10;i++ {
s=append(s,i)
}
注意这里会产生底层数组的扩张
建议: 可以使用nil切片,它们多数的时候都是足够快的。

空字典

varm map[t]u
len(m) // 0
forrange m// 迭代0值
v,ok:=m[i] // 零值,false
m[i] =x// panic: assignment to entry in nil map (指定进入到一个空m字典中)

funcNewGet(urlstring,headers map[string]string (http.*Request,error) {
req,err=http.NewRequest(http.MenthodGet,url,nil)
 iferr!= nil {
 return nil,err
 }

 fork,v:=range headers{
req.Header.Set(k,v)
 }

 returnreq, 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通道

varc chan t
<-c// 永远阻塞
c<-x// 永远阻塞
close(c) // panic: 关闭一个nil通道

关闭的通道
varc chan t
v,ok<-c// 零值, false
c<-x//panic: 发送数据到一个关闭的通道
close(c) // panic: 关闭一个nil通道

关闭一个通道
casev,ok:= <-a:
if !ok{
a= nil 
fmt.Println("a is now closed")
}

使用一个nil通道可以失能一个select

nil 函数

函数类型是一类公民在go中

函数也可以做为结构体的字段

它们需要一个零值,逻辑上可以叫做nil

typeFoo struct {
f func()error
 }

nil函数做为默认值
懒惰的初始化变量
同时也暗示着默认的行为

funcNewServer(logger func(string, ...interface{}){
 iflogger== nil {
 //实现我们说的默认行为
logger=log.Printf
 }
logger("initializing %s",os.Getenv("hostname"))
 ...
}
)

接口

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

iferr!= nil {
 // task
}

为什么*person不等于nil空口

summer

typeSummer interface {
func sum() int 
}

注意:t要实现sum()方法才能赋值给s接口类型
vat t*tree
varsSummer =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
 }
 returns
}

vari ints
varsSummer =i
fmt.Println(i== nil,s.Sum()) // true, 0

说明了: nil值是可以实现接口的

nil值和默认值 
func doSum(sSummer) int {
 ifs== nil {
 return 0
 }
 returns.sum()
}


vart*tree
doSum(t) // (*tree, nil)

vari ints
doSum(i) // (ints, nil)

doSum(nil) // (nil, nil)

Summer 接口的变化

建议使用nil接口去通知默认事件

nil是有用的

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

最后

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

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章