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