Golang中interface內(nèi)部構(gòu)造與面試真題分析

原創(chuàng)聲明
作者:劉丹冰Aceld, 微信公眾號(hào)同名

(1) interface的賦值問題

以下代碼能編譯過去嗎?為什么?

package main

import (
    "fmt"
)

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
    if think == "love" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var peo People = Stduent{}
    think := "love"
    fmt.Println(peo.Speak(think))
}

繼承與多態(tài)的特點(diǎn)

在golang中對(duì)多態(tài)的特點(diǎn)體現(xiàn)從語法上并不是很明顯。

我們知道發(fā)生多態(tài)的幾個(gè)要素:

1、有interface接口,并且有接口定義的方法。

2、有子類去重寫interface的接口。

3、有父類指針指向子類的具體對(duì)象

那么,滿足上述3個(gè)條件,就可以產(chǎn)生多態(tài)效果,就是,父類指針可以調(diào)用子類的具體方法。

所以上述代碼報(bào)錯(cuò)的地方在var peo People = Stduent{}這條語句, Student{}已經(jīng)重寫了父類People{}中的Speak(string) string方法,那么只需要用父類指針指向子類對(duì)象即可。

所以應(yīng)該改成var peo People = &Student{} 即可編譯通過。(People為interface類型,就是指針類型)

(2) interface的內(nèi)部構(gòu)造(非空接口iface情況)

以下代碼打印出來什么內(nèi)容,說出為什么。

package main

import (
    "fmt"
)

type People interface {
    Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
    var stu *Student
    return stu
}

func main() {
    if live() == nil {
        fmt.Println("AAAAAAA")
    } else {
        fmt.Println("BBBBBBB")
    }
}

結(jié)果

BBBBBBB

分析:

我們需要了解interface的內(nèi)部結(jié)構(gòu),才能理解這個(gè)題目的含義。

interface在使用的過程中,共有兩種表現(xiàn)形式

一種為空接口(empty interface),定義如下:

var MyInterface interface{}

另一種為非空接口(non-empty interface), 定義如下:

type MyInterface interface {
        function()
}

這兩種interface類型分別用兩種struct表示,空接口為eface, 非空接口為iface.
!](https://upload-images.jianshu.io/upload_images/11093205-390416d69864055e.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


空接口eface

空接口eface結(jié)構(gòu),由兩個(gè)屬性構(gòu)成,一個(gè)是類型信息_type,一個(gè)是數(shù)據(jù)信息。其數(shù)據(jù)結(jié)構(gòu)聲明如下:

type eface struct {      //空接口
    _type *_type         //類型信息
    data  unsafe.Pointer //指向數(shù)據(jù)的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)
}

_type屬性:是GO語言中所有類型的公共描述,Go語言幾乎所有的數(shù)據(jù)結(jié)構(gòu)都可以抽象成 _type,是所有類型的公共描述,type負(fù)責(zé)決定data應(yīng)該如何解釋和操作,type的結(jié)構(gòu)代碼如下:

type _type struct {
    size       uintptr  //類型大小
    ptrdata    uintptr  //前綴持有所有指針的內(nèi)存大小
    hash       uint32   //數(shù)據(jù)hash值
    tflag      tflag
    align      uint8    //對(duì)齊
    fieldalign uint8    //嵌入結(jié)構(gòu)體時(shí)的對(duì)齊
    kind       uint8    //kind 有些枚舉值kind等于0是無效的
    alg        *typeAlg //函數(shù)指針數(shù)組,類型實(shí)現(xiàn)的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

data屬性: 表示指向具體的實(shí)例數(shù)據(jù)的指針,他是一個(gè)unsafe.Pointer類型,相當(dāng)于一個(gè)C的萬能指針void*


非空接口iface

iface 表示 non-empty interface 的數(shù)據(jù)結(jié)構(gòu),非空接口初始化的過程就是初始化一個(gè)iface類型的結(jié)構(gòu),其中data的作用同eface的相同,這里不再多加描述。

type iface struct {
  tab  *itab
  data unsafe.Pointer
}

iface結(jié)構(gòu)中最重要的是itab結(jié)構(gòu)(結(jié)構(gòu)如下),每一個(gè) itab 都占 32 字節(jié)的空間。itab可以理解為pair<interface type, concrete type> 。itab里面包含了interface的一些關(guān)鍵信息,比如method的具體實(shí)現(xiàn)。

type itab struct {
  inter  *interfacetype   // 接口自身的元信息
  _type  *_type           // 具體類型的元信息
  link   *itab
  bad    int32
  hash   int32            // _type里也有一個(gè)同樣的hash,此處多放一個(gè)是為了方便運(yùn)行接口斷言
  fun    [1]uintptr       // 函數(shù)指針,指向具體類型所實(shí)現(xiàn)的方法
}

其中值得注意的字段,個(gè)人理解如下:

  1. interface type包含了一些關(guān)于interface本身的信息,比如package path,包含的method。這里的interfacetype是定義interface的一種抽象表示。
  2. type表示具體化的類型,與eface的 type類型相同。
  3. hash字段其實(shí)是對(duì)_type.hash的拷貝,它會(huì)在interface的實(shí)例化時(shí),用于快速判斷目標(biāo)類型和接口中的類型是否一致。另,Go的interface的Duck-typing機(jī)制也是依賴這個(gè)字段來實(shí)現(xiàn)。
  4. fun字段其實(shí)是一個(gè)動(dòng)態(tài)大小的數(shù)組,雖然聲明時(shí)是固定大小為1,但在使用時(shí)會(huì)直接通過fun指針獲取其中的數(shù)據(jù),并且不會(huì)檢查數(shù)組的邊界,所以該數(shù)組中保存的元素?cái)?shù)量是不確定的。

所以,People擁有一個(gè)Show方法的,屬于非空接口,People的內(nèi)部定義應(yīng)該是一個(gè)iface結(jié)構(gòu)體

type People interface {
    Show()  
}  
func live() People {
    var stu *Student
    return stu      
}     

stu是一個(gè)指向nil的空指針,但是最后return stu 會(huì)觸發(fā)匿名變量 People = stu值拷貝動(dòng)作,所以最后live()放回給上層的是一個(gè)People insterface{}類型,也就是一個(gè)iface struct{}類型。 stu為nil,只是iface中的data 為nil而已。 但是iface struct{}本身并不為nil.

所以如下判斷的結(jié)果為BBBBBBB

func main() {   
    if live() == nil {  
        fmt.Println("AAAAAAA")      
    } else {
        fmt.Println("BBBBBBB")
    }
}

(3) interface內(nèi)部構(gòu)造(空接口eface情況)

下面代碼結(jié)果為什么?

func Foo(x interface{}) {
    if x == nil {
        fmt.Println("empty interface")
        return
    }
    fmt.Println("non-empty interface")
}
func main() {
    var p *int = nil
    Foo(p)
}

結(jié)果

non-empty interface

分析

不難看出,Foo()的形參x interface{}是一個(gè)空接口類型eface struct{}。

在執(zhí)行Foo(p)的時(shí)候,觸發(fā)x interface{} = p語句,所以此時(shí) x結(jié)構(gòu)如下。

所以 x 結(jié)構(gòu)體本身不為nil,而是data指針指向的p為nil。


(4) inteface{}與*interface{}

ABCD中哪一行存在錯(cuò)誤?

type S struct {
}

func f(x interface{}) {
}

func g(x *interface{}) {
}

func main() {
    s := S{}
    p := &s
    f(s) //A
    g(s) //B
    f(p) //C
    g(p) //D
}

結(jié)果

B、D兩行錯(cuò)誤
B錯(cuò)誤為: cannot use s (type S) as type *interface {} in argument to g:
    *interface {} is pointer to interface, not interface
    
D錯(cuò)誤為:cannot use p (type *S) as type *interface {} in argument to g:
    *interface {} is pointer to interface, not interface

看到這道題需要第一時(shí)間想到的是Golang是強(qiáng)類型語言,interface是所有g(shù)olang類型的父類 函數(shù)中func f(x interface{})interface{}可以支持傳入golang的任何類型,包括指針,但是函數(shù)func g(x *interface{})只能接受*interface{}

如果掌握interface構(gòu)造,建議看下一篇文章
使用Golang的interface接口設(shè)計(jì)原則


關(guān)于作者:

劉丹冰Aceld (微信公眾號(hào)同名)
mail: danbing.at@gmail.com
github: https://github.com/aceld
原創(chuàng)書籍: https://www.kancloud.cn/@aceld


文章推薦

開源軟件作品

(原創(chuàng)開源)Zinx-基于Golang輕量級(jí)服務(wù)器并發(fā)框架-完整版(附教程視頻)

(原創(chuàng)開源)Lars-基于C++負(fù)載均衡遠(yuǎn)程調(diào)度系統(tǒng)-完整版

精選文章

典藏版-Golang調(diào)度器GMP原理與調(diào)度全分析

典藏版-Golang三色標(biāo)記、混合寫屏障GC模式圖文全分析

最常用的調(diào)試 golang 的 bug 以及性能問題的實(shí)踐方法?

Golang中的Defer必掌握的7知識(shí)點(diǎn)

Golang中的局部變量“何時(shí)棧?何時(shí)堆?”

使用Golang的interface接口設(shè)計(jì)原則

流?I/O操作?阻塞?epoll?

深入淺出Golang的協(xié)程池設(shè)計(jì)

Go語言構(gòu)建微服務(wù)一站式解決方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容