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