版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 緊固件鐓鍛工操作規(guī)范評(píng)優(yōu)考核試卷含答案
- 集成電路管殼制造工誠(chéng)信測(cè)試考核試卷含答案
- 凹版制版員崗前常識(shí)考核試卷含答案
- 井下水采工常識(shí)能力考核試卷含答案
- 拖拉機(jī)電器裝試工成果轉(zhuǎn)化水平考核試卷含答案
- 沼氣物管員標(biāo)準(zhǔn)化競(jìng)賽考核試卷含答案
- 磁記錄材料涂布工安全實(shí)操競(jìng)賽考核試卷含答案
- 酒店員工績(jī)效目標(biāo)設(shè)定與考核制度
- 酒店客房鑰匙卡遺失備案制度
- 蠟微粉及特種粉體技術(shù)改造項(xiàng)目環(huán)境影響報(bào)告表
- 2026年及未來(lái)5年市場(chǎng)數(shù)據(jù)中國(guó)集裝箱物流行業(yè)市場(chǎng)發(fā)展數(shù)據(jù)監(jiān)測(cè)及投資戰(zhàn)略規(guī)劃報(bào)告
- 中小學(xué)人工智能教育三年發(fā)展規(guī)劃(2026-2028)7500字完整方案目標(biāo)務(wù)實(shí)真能落地
- 七年級(jí)地理下冊(cè)(人教版)東半球其他的國(guó)家和地區(qū)-歐洲西部自然環(huán)境教學(xué)設(shè)計(jì)
- 口腔現(xiàn)場(chǎng)義診培訓(xùn)
- 學(xué)校中層管理崗位職責(zé)及分工明細(xì)(2026年版)
- 江蘇省南京市六校聯(lián)合體2026屆高一數(shù)學(xué)第一學(xué)期期末監(jiān)測(cè)試題含解析
- 莆田春節(jié)習(xí)俗介紹
- 就業(yè)部門(mén)內(nèi)控制度
- 2026屆江蘇省徐州市侯集高級(jí)中學(xué)高一上數(shù)學(xué)期末復(fù)習(xí)檢測(cè)試題含解析
- 抗洪搶險(xiǎn)先進(jìn)事跡2023
- 鋁材廠煲模作業(yè)指導(dǎo)書(shū)
評(píng)論
0/150
提交評(píng)論