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

再來一個(gè)strcut :

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

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

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

1. Go的文檔中說到,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è)成員來實(shí)現(xiàn),一個(gè)類型和一個(gè)值

看這里有官方明確說明

在底層咬荷,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宴咧,無論該指針的值是什么:(int, nil)根灯。 因此,這樣的接口值會(huì)是非 nil 的,即使在該指針的內(nèi)部為 nil烙肺。

來看看interface倒底是什么纳猪。會(huì)用到反射,關(guān)于反射的文章你可以自己下來學(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
}

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

我們暫且用這種二元組的方式來描述,二元組的第一個(gè)成員為type星著,第二個(gè)成員為data购笆。第二條打印語句輸出的是: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)該變量指針只是聲明笛质,但并沒有指向任何地址泉沾,所以 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. 來看一個(gè) error 的例子吧

有時(shí)候您想自定義一個(gè)返回錯(cuò)誤的函數(shù)來做這個(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子界睁,更是在濱河造成了極大的恐慌觉增,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翻斟,死亡現(xiàn)場離奇詭異逾礁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)杨赤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門敞斋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疾牲,你說我怎么就攤上這事植捎。” “怎么了阳柔?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵焰枢,是天一觀的道長。 經(jīng)常有香客問我舌剂,道長济锄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任霍转,我火速辦了婚禮荐绝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘避消。我一直安慰自己低滩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布岩喷。 她就那樣靜靜地躺著恕沫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纱意。 梳的紋絲不亂的頭發(fā)上婶溯,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音偷霉,去河邊找鬼迄委。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腾它,可吹牛的內(nèi)容都是我干的跑筝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞒滴,長吁一口氣:“原來是場噩夢啊……” “哼曲梗!你這毒婦竟也來了赞警?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤虏两,失蹤者是張志新(化名)和其女友劉穎愧旦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體定罢,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笤虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖凫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼蚯。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惠况,靈堂內(nèi)的尸體忽然破棺而出遭庶,到底是詐尸還是另有隱情,我是刑警寧澤稠屠,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布峦睡,位于F島的核電站,受9級(jí)特大地震影響权埠,放射性物質(zhì)發(fā)生泄漏榨了。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一攘蔽、第九天 我趴在偏房一處隱蔽的房頂上張望龙屉。 院中可真熱鬧,春花似錦满俗、人聲如沸叔扼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鳍咱,卻和暖如春降盹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谤辜。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蓄坏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丑念。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓涡戳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脯倚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渔彰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容