27

Golang 关于 nil 的认识

 5 years ago
source link: https://studygolang.com/articles/14287?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Golang 关于 nil 的认识

1. 什么是 nil ?

大家都清楚,当你声明了一个变量 但却还并木优赋值时,golang中会自动给你的变量类型给一个对应的默认零值。这是每种类型对应的零值:

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil

再来一个strcut :

type Person struct {
  Age int
  Name string
  Friends []Person
}

var p Person // Person{0, "", nil}

变量p只声明但没有赋值,所以p的所有字段都有对应的零值。

1. Go的文档中说到,nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,并不是GO 的关键字之一

2. nil只能赋值给指针、channel、func、interface、map或slice类型的变量 (非基础类型) 否则会引发 panic

2. 那么 nil 有何用?

pointers

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

指针表示指向内存的地址,如果对为nil的指针进行解引用的话就会导致panic。

interface 与 nil (重点讲解)

nil只能赋值给指针、channel、func、interface、map或slice类型的变量。如果未遵循这个规则,则会引发panic。

在底层,interface作为两个成员来实现,一个类型和一个值

看这里有官方明确说明

在底层,interface作为两个成员实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。对于 int 值3, 一个接口值示意性地包含(int, 3)。

只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 *int 类型的指针,则内部类型将为 int,无论该指针的值是什么:( int, nil)。 因此,这样的接口值会是非 nil 的,即使在该指针的内部为 nil。

来看看interface倒底是什么。会用到反射,关于反射的文章你可以自己下来学习,也可参考这篇文章

反射文章讲解

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var val interface{} = int64(58)
    fmt.Println(reflect.TypeOf(val)) // int64
    val = 50
    fmt.Println(reflect.TypeOf(val)) // int
}

在上面的例子中,第一条打印语句输出的是:int64。这是因为已经显示的将类型为int64的数据58赋值给了interface类型的变量val,所以val的底层结构应该是:(int64, 58)。

我们暂且用这种二元组的方式来描述,二元组的第一个成员为type,第二个成员为data。第二条打印语句输出的是:int。这是因为字面量的整数在golang中默认的类型是int,所以这个时候val的底层结构就变成了:(int, 50)。

请看下面的代码:

package main

import (
    "fmt"
    "reflect"
)

type MyError struct {
    Name string
}

func (e *MyError) Error() string {
    return "a"
}

func main() {

    // nil只能赋值给指针、channel、func、interface、map或slice类型的变量 (非基础类型) 否则会引发 panic
    var a *MyError                          // 这里不是 interface 类型  => 而是 一个值为 nil 的指针变量 a
    fmt.Printf("%+v\n", reflect.TypeOf(a))  // *main.MyError
    fmt.Printf("%#v\n", reflect.ValueOf(a)) // a => (*main.MyError)(nil)
    fmt.Printf("%p %#v\n", &a, a)           // 0xc42000c028 (*main.MyError)(nil)
    i := reflect.ValueOf(a)
    fmt.Println(i.IsNil()) // true

    if a == nil {
        fmt.Println("a == nil") //  a == nil
    } else {
        fmt.Println("a != nil")
    }

    fmt.Println("a Error():", a.Error()) //a 为什么 a 为 nil 也能调用方法?(指针类型的值为nil 也可以调用方法!但不能进行赋值操作!如下:)
    // a.Name = "1"           // panic: runtime error: invalid memory address or nil pointer dereference

    var b error = a

    // 为什么 a 为 nil 给了 b 而 b != nil ??? => error 是 interface 类型,type = *a.MyError  data = nil
    fmt.Printf("%+v\n", reflect.TypeOf(b))  // type => *main.MyError
    fmt.Printf("%+v\n", reflect.ValueOf(b)) // data => a == nil
    if b == nil {
        fmt.Println("b == nil")
    } else {
        fmt.Println("b != nil")
    }
    fmt.Println("b Error():", b.Error()) // a

}

1. 首先 a 是个变量指针,(注意这里 a 并不是interface)该变量指针只是声明,但并没有指向任何地址,所以 a == nil

2. 指针类型的值为 nil ,也能调用方法,但不能进行赋值操作,否则就会引起 panic

3. var b error = a, 这时这里的b 是一个interface, 即应该要满足 上面提到的 interface 与 nil 的关系,即 只有当它的 type 和 data 都为 nil 时才 = nil! (b 是有类型的 为 *main.MyError) 所以最后会有 b != nil

3. 来看一个 error 的例子吧

有时候您想自定义一个返回错误的函数来做这个事,可能会写出以下代码:

package main  
   
import (  
    "fmt"  
)  
   
type data struct{}  
   
func (this *data) Error() string { return "" }  
   
func test() error {  
    var p *data = nil  
    return p  
}  
   
func main() {  
    var e error = test()  
    if e == nil {  
        fmt.Println("e is nil")  
    } else {  
        fmt.Println("e is not nil")   //  e is not nil
    }  
}

分析:

error是一个接口类型,test方法中返回的指针p虽然数据是nil,但是由于它被返回成包装的error类型,也即它是有类型的。所以它的底层结构应该是(*data, nil),很明显它是非nil的。可以打印观察下底层结构数据:

package main

import (
    "fmt"
    "unsafe"
)

type data struct{}

func (*data) Error() string { return "" }

func test() error {
    var p *data = nil // (*data, nil)
    return p
}

type aa struct {
    itab uintptr
    data uintptr
}

func main() {
    var e error = test()
    d := (*aa)(unsafe.Pointer(&e))
    dd := (*struct {
        itab uintptr
        data uintptr
    })(unsafe.Pointer(&e))

    fmt.Println(d)          // &{17636960 0}
    fmt.Printf("%+v\n", d)  // &{itab:17644608 data:0}
    fmt.Printf("%#v\n", d)  // &main.aa{itab:0x10d3e00, data:0x0}
    fmt.Printf("%#v\n", dd) // &struct { itab uintptr; data uintptr }{itab:0x10d3ca0, data:0x0}
}

正确的做法应该是:

package main  
   
import (  
    "fmt"  
)  
   
type data struct{}  
   
func (this *data) Error() string { return "" }  
   
func bad() bool {  
    return true  
}  
   
func test() error {  
    var p *data = nil  
    if bad() {  
        return p  
    }  
    return nil   // 直接抛 nil
}  
   
func main() {  
    var e error = test()  
    if e == nil {  
        fmt.Println("e is nil")  
    } else {  
        fmt.Println("e is not nil")  
    }  
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK