Golang 關(guān)于 nil 的認(rèn)識(shí)

Golang 關(guān)于 nil 的認(rèn)識(shí)

1. 什么是 nil ?

大家都清楚,當(dāng)你聲明了一個(gè)變量 但卻還并木優(yōu)賦值時(shí),golang中會(huì)自動(dòng)給你的變量類型給一個(gè)對(duì)應(yīng)的默認(rèn)零值。這是每種類型對(duì)應(yīng)的零值:

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

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

再來(lái)一個(gè)strcut :

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

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

變量p只聲明但沒(méi)有賦值,所以p的所有字段都有對(duì)應(yīng)的零值。

1. Go的文檔中說(shuō)到,nil是預(yù)定義的標(biāo)識(shí)符,代表指針、通道、函數(shù)、接口、映射或切片的零值,并不是GO 的關(guān)鍵字之一

2. nil只能賦值給指針、channel、func、interface、map或slice類型的變量 (非基礎(chǔ)類型) 否則會(huì)引發(fā) panic


2. 那么 nil 有何用?

pointers

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

指針表示指向內(nèi)存的地址,如果對(duì)為nil的指針進(jìn)行解引用的話就會(huì)導(dǎo)致panic。

interface 與 nil (重點(diǎn)講解)

nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個(gè)規(guī)則,則會(huì)引發(fā)panic。

在底層,interface作為兩個(gè)成員來(lái)實(shí)現(xiàn),一個(gè)類型和一個(gè)值

看這里有官方明確說(shuō)明

在底層,interface作為兩個(gè)成員實(shí)現(xiàn):一個(gè)類型和一個(gè)值。該值被稱為接口的動(dòng)態(tài)值, 它是一個(gè)任意的具體值,而該接口的類型則為該值的類型。對(duì)于 int 值3, 一個(gè)接口值示意性地包含(int, 3)。

只有在內(nèi)部值和類型都未設(shè)置時(shí)(nil, nil),一個(gè)接口的值才為 nil。特別是,一個(gè) nil 接口將總是擁有一個(gè) nil 類型。若我們?cè)谝粋€(gè)接口值中存儲(chǔ)一個(gè) *int 類型的指針,則內(nèi)部類型將為 int,無(wú)論該指針的值是什么:(int, nil)。 因此,這樣的接口值會(huì)是非 nil 的,即使在該指針的內(nèi)部為 nil。

來(lái)看看interface倒底是什么。會(huì)用到反射,關(guān)于反射的文章你可以自己下來(lái)學(xué)習(xí),也可參考這篇文章

反射文章講解

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
}

在上面的例子中,第一條打印語(yǔ)句輸出的是:int64。這是因?yàn)橐呀?jīng)顯示的將類型為int64的數(shù)據(jù)58賦值給了interface類型的變量val,所以val的底層結(jié)構(gòu)應(yīng)該是:(int64, 58)。

我們暫且用這種二元組的方式來(lái)描述,二元組的第一個(gè)成員為type,第二個(gè)成員為data。第二條打印語(yǔ)句輸出的是:int。這是因?yàn)樽置媪康恼麛?shù)在golang中默認(rèn)的類型是int,所以這個(gè)時(shí)候val的底層結(jié)構(gòu)就變成了:(int, 50)。

請(qǐng)看下面的代碼:

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類型的變量 (非基礎(chǔ)類型) 否則會(huì)引發(fā) panic
    var a *MyError                          // 這里不是 interface 類型  => 而是 一個(gè)值為 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 也能調(diào)用方法?(指針類型的值為nil 也可以調(diào)用方法!但不能進(jìn)行賦值操作!如下:)
    // 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 是個(gè)變量指針,(注意這里 a 并不是interface)該變量指針只是聲明,但并沒(méi)有指向任何地址,所以 a == nil

2. 指針類型的值為 nil ,也能調(diào)用方法,但不能進(jìn)行賦值操作,否則就會(huì)引起 panic

3. var b error = a, 這時(shí)這里的b 是一個(gè)interface, 即應(yīng)該要滿足 上面提到的 interface 與 nil 的關(guān)系,即 只有當(dāng)它的 type 和 data 都為 nil 時(shí)才 = nil! (b 是有類型的 為 *main.MyError) 所以最后會(huì)有 b != nil


3. 來(lái)看一個(gè) error 的例子吧

有時(shí)候您想自定義一個(gè)返回錯(cuò)誤的函數(shù)來(lái)做這個(gè)事,可能會(huì)寫出以下代碼:

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是一個(gè)接口類型,test方法中返回的指針p雖然數(shù)據(jù)是nil,但是由于它被返回成包裝的error類型,也即它是有類型的。所以它的底層結(jié)構(gòu)應(yīng)該是(*data, nil),很明顯它是非nil的??梢源蛴∮^察下底層結(jié)構(gòu)數(shù)據(jù):


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}
}

正確的做法應(yīng)該是:


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")  
    }  
}  

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容