Golang 關於 nil 的認識

來源:互聯網
上載者:User

Golang 關於 nil 的認識

1. 什麼是 nil ?

大家都清楚,當你聲明了一個變數 但卻還並木優賦值時,golang中會自動給你的變數類型給一個對應的預設零值。這是每種類型對應的零值:

bool      -> false                              numbers -> 0                                 string    -> ""      pointers -> nilslices -> nilmaps -> nilchannels -> nilfunctions -> nilinterfaces -> 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 *intp == 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 mainimport (    "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 mainimport (    "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 mainimport (    "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")      }  }  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.