亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語(yǔ)言interface 與 nil 的比較

 更新時(shí)間:2017年08月18日 08:51:47   作者:lorddeseis  
在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個(gè)規(guī)則,則會(huì)引發(fā)panic。

interface簡(jiǎn)介

Go語(yǔ)言以簡(jiǎn)單易上手而著稱,它的語(yǔ)法非常簡(jiǎn)單,熟悉C++,Java的開發(fā)者只需要很短的時(shí)間就可以掌握Go語(yǔ)言的基本用法。

interface是Go語(yǔ)言里所提供的非常重要的特性。一個(gè)interface里可以定義一個(gè)或者多個(gè)函數(shù),例如系統(tǒng)自帶的io.ReadWriter的定義如下所示:

type ReadWriter interface {
  Read(b []byte) (n int, err error)
  Write(b []byte) (n int, err error)
}

任何類型只要它提供了Read和Write的綁定函數(shù)實(shí)現(xiàn),Go就認(rèn)為這個(gè)類型實(shí)現(xiàn)了這個(gè)interface(duck-type),而不像Java需要開發(fā)者使用implements標(biāo)明。

然而Go語(yǔ)言的interface在使用過(guò)程中卻有一個(gè)特別坑的特性,當(dāng)你比較一個(gè)interface類型的值是否是nil的時(shí)候,這是需要特別注意避免的問(wèn)題。

一次真實(shí)的踩坑

這是我們?cè)贕oWorld分布式游戲服務(wù)器的開發(fā)中,碰到的一個(gè)實(shí)際的bug。由于GoWorld支持多種不同的數(shù)據(jù)庫(kù)(包括MongoDB,Redis等)來(lái)保存服務(wù)端對(duì)象,因此GoWorld在上層提供了一個(gè)統(tǒng)一的對(duì)象存儲(chǔ)接口定義,而不同的對(duì)象數(shù)據(jù)庫(kù)實(shí)現(xiàn)只需要實(shí)現(xiàn)EntityStorage接口所提供的函數(shù)即可。

// EntityStorage defines the interface of entity storage backends
type EntityStorage interface {
 List(typeName string) ([]common.EntityID, error)
 Write(typeName string, entityID common.EntityID, data interface{}) error
 Read(typeName string, entityID common.EntityID) (interface{}, error)
 Exists(typeName string, entityID common.EntityID) (bool, error)
 Close()
 IsEOF(err error) bool
}

以一個(gè)使用Redis作為對(duì)象數(shù)據(jù)庫(kù)的實(shí)現(xiàn)為例,函數(shù)OpenRedis連接Redis數(shù)據(jù)庫(kù)并最終返回一個(gè)redisEntityStorage對(duì)象的指針。

// OpenRedis opens redis as entity storage
func OpenRedis(url string, dbindex int) *redisEntityStorage {
 c, err := redis.DialURL(url)
 if err != nil {
 return nil
 }

 if dbindex >= 0 {
 if _, err := c.Do("SELECT", dbindex); err != nil {
  return nil
 }
 }

 es := &redisEntityStorage{
 c: c,
 }

 return es
}

在上層邏輯中,我們使用OpenRedis函數(shù)連接Redis數(shù)據(jù)庫(kù),并將返回的redisEntityStorage指針賦值個(gè)一個(gè)EntityStorage接口變量,因?yàn)閞edisEntityStorage對(duì)象實(shí)現(xiàn)了EntityStorage接口所定義的所有函數(shù)。

var storageEngine StorageEngine // 這是一個(gè)全局變量
storageEngine = OpenRedis(cfg.Url, dbindex)
if storageEngine != nil {
  // 連接成功
  ...
} else {
  // 連接失敗
  ...
}

上面的代碼看起來(lái)都很正常,OpenRedis在連接Redis數(shù)據(jù)庫(kù)失敗的時(shí)候會(huì)返回nil,然后調(diào)用者將返回值和nil進(jìn)行比較,來(lái)判斷是否連接成功。這個(gè)就是Go語(yǔ)言少有的幾個(gè)深坑之一,因?yàn)椴还躉penRedis函數(shù)是否連接Redis成功,都會(huì)運(yùn)行連接成功的邏輯。

尋找問(wèn)題所在

想要理解這個(gè)問(wèn)題,首先需要理解interface{}變量的本質(zhì)。在Go語(yǔ)言中,一個(gè)interface{}類型的變量包含了2個(gè)指針,一個(gè)指針指向值的類型,另外一個(gè)指針指向?qū)嶋H的值。 我們可以用如下的測(cè)試代碼進(jìn)行驗(yàn)證。

// InterfaceStructure 定義了一個(gè)interface{}的內(nèi)部結(jié)構(gòu)
type InterfaceStructure struct {
 pt uintptr // 到值類型的指針
 pv uintptr // 到值內(nèi)容的指針
}

// asInterfaceStructure 將一個(gè)interface{}轉(zhuǎn)換為InterfaceStructure
func asInterfaceStructure (i interface{}) InterfaceStructure {
 return *(*InterfaceStructure)(unsafe.Pointer(&i))
}

func TestInterfaceStructure(t *testing.T) {
 var i1, i2 interface{}
 var v1 int = 0x0AAAAAAAAAAAAAAA
 var v2 int = 0x0BBBBBBBBBBBBBBB
 i1 = v1
 i2 = v2
 fmt.Printf("sizeof interface{} = %d\n", unsafe.Sizeof(i1))
 fmt.Printf("i1 %x %+v\n", i1, asInterfaceStructure(i1))
 fmt.Printf("i2 %x %+v\n", i2, asInterfaceStructure(i2))
 var nilInterface interface{}
 fmt.Printf("nil interface = %+v\n", asInterfaceStructure(nilInterface))
}

這段代碼的輸出如下:

sizeof interface{} = 16
i1 aaaaaaaaaaaaaaa {pt:5328736 pv:825741282816}
i2 bbbbbbbbbbbbbbb {pt:5328736 pv:825741282824}
nil interface = {pt:0 pv:0}

所以對(duì)于一個(gè)interface{}類型的nil變量來(lái)說(shuō),它的兩個(gè)指針都是0。這是符合Go語(yǔ)言對(duì)nil的標(biāo)準(zhǔn)定義的。在Go語(yǔ)言中,nil是零值(Zero Value),而在Java之類的語(yǔ)言里,null實(shí)際上是空指針。關(guān)于零值和空指針有什么區(qū)別,這里就不再展開了。

當(dāng)我們將一個(gè)具體類型的值賦值給一個(gè)interface類型的變量的時(shí)候,就同時(shí)把類型和值都賦值給了interface里的兩個(gè)指針。如果這個(gè)具體類型的值是nil的話,interface變量依然會(huì)存儲(chǔ)對(duì)應(yīng)的類型指針和值指針。

func TestAssignInterfaceNil(t *testing.T) {
 var p *int = nil
 var i interface{} = p
 fmt.Printf("%v %+v is nil %v\n", i, asInterfaceStructure(i), i == nil)
}

輸入如下:

<nil> {pt:5300576 pv:0} is nil false

可見,在這種情況下,雖然我們把一個(gè)nil值賦值給interface{},但是實(shí)際上interface里依然存了指向類型的指針,所以拿這個(gè)interface變量去和nil常量進(jìn)行比較的話就會(huì)返回false。

如何解決這個(gè)問(wèn)題

想要避開這個(gè)Go語(yǔ)言的坑,我們要做的就是避免將一個(gè)有可能為nil的具體類型的值賦值給interface變量。以上述的OpenRedis為例,一種方法是先對(duì)OpenRedis返回的結(jié)果進(jìn)行非-nil檢查,然后再賦值給interface變量,如下所示。

var storageEngine StorageEngine // 這是一個(gè)全局變量
redis := OpenRedis(cfg.Url, dbindex)
if redis != nil {
  // 連接成功
  storageEngine = redis // 確定redis不是nil之后再賦值給interface變量
} else {
  // 連接失敗
  ...
}

另外一種方法是讓OpenRedis函數(shù)直接返回EntityStorage接口類型的值,這樣就可以把OpenRedis的返回值直接正確賦值給EntityStorage接口變量。

// OpenRedis opens redis as entity storage
func OpenRedis(url string, dbindex int) EntityStorage {
 c, err := redis.DialURL(url)
 if err != nil {
 return nil
 }

 if dbindex >= 0 {
 if _, err := c.Do("SELECT", dbindex); err != nil {
  return nil
 }
 }

 es := &redisEntityStorage{
 c: c,
 }

 return es
}

至于那種方法更好,就見仁見智了。希望大家在實(shí)際項(xiàng)目中不要踩坑,即使踩了也能快速跳出來(lái)!

您可能感興趣的文章:

相關(guān)文章

  • 一文詳解Go語(yǔ)言中切片的底層原理

    一文詳解Go語(yǔ)言中切片的底層原理

    在Go語(yǔ)言中,切片作為一種引用類型數(shù)據(jù),相對(duì)數(shù)組而言是一種動(dòng)態(tài)長(zhǎng)度的數(shù)據(jù)類型,使用的場(chǎng)景也是非常多,所以本文主要來(lái)和大家聊聊切片的底層原理,需要的可以參考一下
    2023-06-06
  • go swagger生成接口文檔使用教程

    go swagger生成接口文檔使用教程

    這篇文章主要為大家介紹了go swagger生成接口文檔使用教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang信號(hào)處理及如何實(shí)現(xiàn)進(jìn)程的優(yōu)雅退出詳解

    Golang信號(hào)處理及如何實(shí)現(xiàn)進(jìn)程的優(yōu)雅退出詳解

    這篇文章主要給大家介紹了關(guān)于Golang信號(hào)處理及如何實(shí)現(xiàn)進(jìn)程的優(yōu)雅退出的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-03-03
  • golang中獲取變量類型的方法總結(jié)

    golang中獲取變量類型的方法總結(jié)

    golang中是沒有提供內(nèi)置函數(shù)來(lái)獲取變量的類型的,但是通過(guò)一定的方式也可以獲取,下面主要給大家介紹了幾個(gè)golang獲取變量類型的幾種方式,需要的朋友可以參考下
    2025-03-03
  • Golang的命名規(guī)范及最佳實(shí)踐(推薦!)

    Golang的命名規(guī)范及最佳實(shí)踐(推薦!)

    這篇文章主要給大家介紹了關(guān)于Golang的命名規(guī)范及最佳實(shí)踐的相關(guān)資料,命名規(guī)則涉及變量、常量、全局函數(shù)、結(jié)構(gòu)、接口、方法等的命名,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • 深入解析Go template模板使用詳解

    深入解析Go template模板使用詳解

    這篇文章主要介紹了深入解析Go template模板使用詳解,需要的朋友可以參考下
    2022-04-04
  • K8s部署發(fā)布Golang應(yīng)用程序的實(shí)現(xiàn)方法

    K8s部署發(fā)布Golang應(yīng)用程序的實(shí)現(xiàn)方法

    本文主要介紹了K8s部署發(fā)布Golang應(yīng)用程序的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • Golang 并發(fā)以及通道的使用方式

    Golang 并發(fā)以及通道的使用方式

    這篇文章主要介紹了Golang 并發(fā)以及通道的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • 基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)

    基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)

    對(duì)于聊天服務(wù),想必大家都不會(huì)陌生,因?yàn)樵谖覀兊纳钪薪?jīng)常會(huì)用到,本文我們用?Go?并發(fā)來(lái)實(shí)現(xiàn)一個(gè)聊天服務(wù)器,這個(gè)程序可以讓一些用戶通過(guò)服務(wù)器向其它所有用戶廣播文本消息,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • 實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解

    實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解

    這篇文章主要為大家介紹了實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評(píng)論