Go疑難雜癥講解之為什么nil不等于nil_第1頁(yè)
Go疑難雜癥講解之為什么nil不等于nil_第2頁(yè)
Go疑難雜癥講解之為什么nil不等于nil_第3頁(yè)
Go疑難雜癥講解之為什么nil不等于nil_第4頁(yè)
Go疑難雜癥講解之為什么nil不等于nil_第5頁(yè)
已閱讀5頁(yè),還剩6頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第Go疑難雜癥講解之為什么nil不等于nil目錄現(xiàn)象尋找原因總結(jié)

現(xiàn)象

在日常開(kāi)發(fā)中,可能一不小心就會(huì)掉進(jìn)Go語(yǔ)言的某些陷阱里,而本文要介紹的nilnil問(wèn)題,便是其中一個(gè),初看起來(lái)會(huì)讓人覺(jué)得很詭異,摸不著頭腦。

先來(lái)看個(gè)例子:

typeCustomizedErrorstruct{

ErrorCodeint

Msgstring

func(e*CustomizedError)Error()string{

returnfmt.Sprintf("errcode:%d,msg:%s",e.ErrorCode,e.Msg)

}

funcmain(){

txn,err:=startTx()

iferr!=nil{

log.Fatalf("errstartingtx:%v",err)

iferr=txn.doUpdate();err!=nil{

log.Fatalf("errupdating:%v",err)

iferr=mit();err!=nil{

log.Fatalf("errcommitting:%v",err)

fmt.Println("success!")

typetxstruct{}

funcstartTx()(*tx,error){

returntx{},nil

func(*tx)doUpdate()*CustomizedError{

returnnil

func(*tx)commit()error{

returnnil

}

這是一個(gè)簡(jiǎn)化過(guò)了的例子,在上述代碼中,我們創(chuàng)建了一個(gè)事務(wù),然后做了一些更新,在更新過(guò)程中如果發(fā)生了錯(cuò)誤,希望返回對(duì)應(yīng)的錯(cuò)誤碼和提示信息。

如果感興趣的話,可以在這個(gè)地址在線運(yùn)行這份代碼:

GoPlayground-TheGoProgrammingLanguage

看起來(lái)每個(gè)方法都會(huì)返回nil,應(yīng)該能順利走到最后一行,輸出success才對(duì),但實(shí)際上,輸出的卻是:

errupdating:nil

尋找原因

為什么明明返回的是nil,卻被判定為errnil呢?難道這個(gè)nil也有什么奇妙之處?

這就需要我們來(lái)更深入一點(diǎn)了解error本身了。在Go語(yǔ)言中,error是一個(gè)interface,內(nèi)部含有一個(gè)Error()函數(shù),返回一個(gè)字符串,接口的描述如下:

//Theerrorbuilt-ininterfacetypeistheconventionalinterfacefor

//representinganerrorcondition,withthenilvaluerepresentingnoerror.

typeerrorinterface{

Error()string

}

而對(duì)于一個(gè)變量來(lái)說(shuō),它有兩個(gè)要素,一個(gè)是typeT,一個(gè)是valueV,如下圖所示:

來(lái)看一個(gè)簡(jiǎn)單的例子:

varitinterface{}

fmt.Println(reflect.TypeOf(it),reflect.ValueOf(it))//nilinvalidreflect.Value

it=1

fmt.Println(reflect.TypeOf(it),reflect.ValueOf(it))//int1

it="hello"

fmt.Println(reflect.TypeOf(it),reflect.ValueOf(it))//stringhello

vars*string

it=s

fmt.Println(reflect.TypeOf(it),reflect.ValueOf(it))//*stringnil

ss:="hello"

it=ss

fmt.Println(reflect.TypeOf(it),reflect.ValueOf(it))//*string0xc000096560

在給一個(gè)interface變量賦值前,T和V都是nil,但給它賦值后,不僅會(huì)改變它的值,還會(huì)改變它的類(lèi)型。

當(dāng)把一個(gè)值為nil的字符串指針賦值給它后,雖然它的值是V=nil,但它的類(lèi)型T卻變成了*string。

此時(shí)如果拿它來(lái)跟nil比較,結(jié)果就會(huì)是不相等,因?yàn)橹挥挟?dāng)這個(gè)interface變量的類(lèi)型和值都未被設(shè)置時(shí),它才真正等于nil。

再來(lái)看看之前的例子中,err變量的T和V是如何變化的:

funcmain(){

txn,err:=startTx()

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))

iferr!=nil{

log.Fatalf("errstartingtx:%v",err)

iferr=txn.doUpdate();err!=nil{

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))

log.Fatalf("errupdating:%v",err)

iferr=mit();err!=nil{

log.Fatalf("errcommitting:%v",err)

fmt.Println("success!")

}

輸出如下:

nilinvalidreflect.Value

*err.CustomizedErrornil

在一開(kāi)始,我們給err初始化賦值時(shí),startTx函數(shù)返回的是一個(gè)error接口類(lèi)型的nil。此時(shí)查看其類(lèi)型T和值V時(shí),都會(huì)是nil。

txn,err:=startTx()

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))//nilinvalidreflect.Value

funcstartTx()(*tx,error){

returntx{},nil

}

而在調(diào)用doUpdate時(shí),會(huì)將一個(gè)*CustomizedError類(lèi)型的nil值賦值給了它,它的類(lèi)型T便成了*CustomizedError,V是nil。

err=txn.doUpdate()

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))//*err.CustomizedErrornil

所以在做errnil的比較時(shí),err的類(lèi)型T已經(jīng)不是nil,前面已經(jīng)說(shuō)過(guò),只有當(dāng)一個(gè)接口變量的T和V同時(shí)為nil時(shí),這個(gè)變量才會(huì)被判定為nil,所以該不等式會(huì)判定為true。

要修復(fù)這個(gè)問(wèn)題,其實(shí)最簡(jiǎn)單的方法便是在調(diào)用doUpdate方法時(shí)給err進(jìn)行重新聲明:

iferr:=txn.doUpdate();err!=nil{

log.Fatalf("errupdating:%v",err)

}

此時(shí),err其實(shí)成了一個(gè)新的結(jié)構(gòu)體指針變量,而不再是一個(gè)interface類(lèi)型變量,類(lèi)型為*CustomizedError,且值為nil,所以做errnil的比較時(shí)結(jié)果就是將是false。

問(wèn)題到這里似乎就告一段落了,但,再仔細(xì)想想,就會(huì)發(fā)現(xiàn)這其中似乎還是漏掉了一環(huán)。

如果給一個(gè)interface類(lèi)型的變量賦值時(shí),會(huì)同時(shí)改變它的類(lèi)型T和值V,那跟nil比較時(shí)為什么不是跟它的新類(lèi)型對(duì)應(yīng)的nil比較呢?

事實(shí)上,interface變量跟普通變量確實(shí)有一定區(qū)別,一個(gè)非空接口interface(即接口中存在函數(shù)方法)初始化的底層數(shù)據(jù)結(jié)構(gòu)是iface,一個(gè)空接口變量對(duì)應(yīng)的底層結(jié)構(gòu)體為eface。

typeifacestruct{

tab*itab

dataunsafe.Pointer

typeefacestruct{

_type*_type

dataunsafe.Pointer

}

tab中存放的是類(lèi)型、方法等信息。data指針指向的iface綁定對(duì)象的原始數(shù)據(jù)的副本。

再來(lái)看一下itab的結(jié)構(gòu):

//layoutofItabknowntocompilers

//allocatedinnon-garbage-collectedmemory

//Needstobeinsyncwith

//../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.

typeitabstruct{

inter*interfacetype

_type*_type

hashuint32//copyof_type.hash.Usedfortypeswitches.

_[4]byte//用于內(nèi)存對(duì)齊

fun[1]uintptr//variablesized.fun[0]==0means_typedoesnotimplementinter.

}

itab中一共包含5個(gè)字段,inner字段存的是初始化interface時(shí)的靜態(tài)類(lèi)型。_type存的是interface對(duì)應(yīng)具體對(duì)象的類(lèi)型,當(dāng)interface變量被賦值后,這個(gè)字段便會(huì)變成被賦值的對(duì)象的類(lèi)型。

itab中的_type和iface中的data便分別對(duì)應(yīng)interface變量的T和V,_type是這個(gè)變量對(duì)應(yīng)的類(lèi)型,data是這個(gè)變量的值。在之前的賦值測(cè)試中,通過(guò)reflect.TypeOf與reflect.ValueOf方法獲取到的信息也分別來(lái)自這兩個(gè)字段。

這里的hash字段和_type中存的hash字段是完全一致的,這么做的目的是為了類(lèi)型斷言。

fun是一個(gè)函數(shù)指針,它指向的是具體類(lèi)型的函數(shù)方法,在這個(gè)指針對(duì)應(yīng)內(nèi)存地址的后面依次存儲(chǔ)了多個(gè)方法,利用指針偏移便可以找到它們。

再來(lái)看看interfacetype的結(jié)構(gòu):

typeinterfacetypestruct{

typ_type

pkgpathname

mhdr[]imethod

}

這其中也有一個(gè)_type字段,來(lái)表示interface變量的初始類(lèi)型。

看到這里,之前的疑問(wèn)便開(kāi)始清晰起來(lái),一個(gè)interface變量實(shí)際上有兩個(gè)類(lèi)型,一個(gè)是初始化時(shí)賦值時(shí)對(duì)應(yīng)的interface類(lèi)型,一個(gè)是賦值具體對(duì)象時(shí),對(duì)象的實(shí)際類(lèi)型。

了解了這些之后,我們?cè)賮?lái)看一下之前的例子:

txn,err:=startTx()

這里先對(duì)err進(jìn)行初始化賦值,此時(shí),它的er.typ對(duì)應(yīng)的類(lèi)型信息就是erroritab._type仍為nil。

err=txn.doUpdate()

當(dāng)對(duì)err進(jìn)行重新賦值時(shí),err的itab._type字段會(huì)被賦值成*CustomizedError,所以此時(shí),err變量實(shí)際上是一個(gè)er.typ為error,但實(shí)際類(lèi)型為*CustomizedError,值為nil的接口變量。

把一個(gè)具體類(lèi)型變量與nil比較時(shí),只需要判斷其value是否為nil即可,而把一個(gè)接口類(lèi)型的變量與nil進(jìn)行比較時(shí),還需要判斷其類(lèi)型itab._type是否為nil。

如果想實(shí)際看看被賦值后err對(duì)應(yīng)的iface結(jié)構(gòu),可以把iface相關(guān)的結(jié)構(gòu)體都復(fù)制到同一個(gè)包下,然后通過(guò)unsafe.Pointer進(jìn)行類(lèi)型強(qiáng)轉(zhuǎn),就可以通過(guò)打斷點(diǎn)的方式來(lái)查看了。

funcTestErr(t*testing.T){

txn,err:=startTx()

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))

iferr!=nil{

log.Fatalf("errstartingtx:%v",err)

p:=(*iface)(unsafe.Pointer(err))

fmt.Println(p.data)

iferr=txn.doUpdate();err!=nil{

fmt.Println(reflect.TypeOf(err),reflect.ValueOf(err))

p:=(*iface)(unsafe.Pointer(err))

fmt.Println(p.data)

log.Fatalf("errupdating:%v",err)

iferr=mit();err!=nil{

log.Fatalf("errcommitting:%v",err)

fmt.Println("success!")

}

補(bǔ)充說(shuō)明一下,這里的inter.typ.kind表示的是變量的基本類(lèi)型,其值對(duì)應(yīng)runtime包下的枚舉。

const(

kindBool=1+iota

kindInt

kindInt8

kindInt16

kindInt32

kindIn

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論