@aliasliyu4
2017-01-01T14:29:38.000000Z
字数 4368
阅读 4847
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 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
type Person struct {
AgeYears int
Name string
Friend []Person
}
var p Person //Person{o,"",nil}
除非这个值是预先标示为nil否则它就没有类型
a := false // 布尔
a := " " // 字符串
a := 0 // 整形
a := 0.0 // 浮点型
a := nil // 使用了无类型的nil
nil是一个预先标示好的表示指针,通道,函数,接口,字典或者切片类型的0值。
在golang中的nil种类
pointers
slices
maps
channels
functions
interfaces
它们指向内存地址
就像c c++, 但是go也有不同的地方:
空指针
[]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
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 没有赋值,即使是一个空指针
以下这些如何变得有用
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
}
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接口去通知默认事件
pointers //方法可以在一个nil指针上调用
slices //有效的0值
maps //只读
channels //本质是一个并发模型
functions //需要实现
interfaces //最常见的是做为go中的信号
让我们不再去逃避nil,而是拥抱它!