Golang中interface內部構造與面試真題分析

原創聲明
做者: 劉丹冰Aceld, 微信公衆號同名

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

繼承與多態的特色git

在golang中對多態的特色體現從語法上並非很明顯。github

咱們知道發生多態的幾個要素:golang

一、有interface接口,而且有接口定義的方法。數組

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

三、有父類指針指向子類的具體對象服務器

那麼,知足上述3個條件,就能夠產生多態效果,就是,父類指針能夠調用子類的具體方法。微信

因此上述代碼報錯的地方在var peo People = Stduent{}這條語句, Student{}已經重寫了父類People{}中的Speak(string) string方法,那麼只須要用父類指針指向子類對象便可。數據結構

因此應該改爲var peo People = &Student{} 便可編譯經過。(People爲interface類型,就是指針類型)併發

(2) interface的內部構造(非空接口iface狀況)

如下代碼打印出來什麼內容,說出爲何。
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")
    }
}

結果負載均衡

BBBBBBB

分析:

咱們須要瞭解interface的內部結構,才能理解這個題目的含義。

interface在使用的過程當中,共有兩種表現形式

一種爲空接口(empty interface),定義以下:

var MyInterface interface{}

另外一種爲非空接口(non-empty interface), 定義以下:

type MyInterface interface {
        function()
}

這兩種interface類型分別用兩種struct表示,空接口爲eface, 非空接口爲iface.


空接口eface

空接口eface結構,由兩個屬性構成,一個是類型信息_type,一個是數據信息。其數據結構聲明以下:

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

_type屬性:是GO語言中全部類型的公共描述,Go語言幾乎全部的數據結構均可以抽象成 _type,是全部類型的公共描述,type負責決定data應該如何解釋和操做,type的結構代碼以下:

type _type struct {
    size       uintptr  //類型大小
    ptrdata    uintptr  //前綴持有全部指針的內存大小
    hash       uint32   //數據hash值
    tflag      tflag
    align      uint8    //對齊
    fieldalign uint8    //嵌入結構體時的對齊
    kind       uint8    //kind 有些枚舉值kind等於0是無效的
    alg        *typeAlg //函數指針數組,類型實現的全部方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

data屬性: 表示指向具體的實例數據的指針,他是一個unsafe.Pointer類型,至關於一個C的萬能指針void*


非空接口iface

iface 表示 non-empty interface 的數據結構,非空接口初始化的過程就是初始化一個iface類型的結構,其中data的做用同eface的相同,這裏再也不多加描述。

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

iface結構中最重要的是itab結構(結構以下),每個 itab 都佔 32 字節的空間。itab能夠理解爲pair<interface type, concrete type> 。itab裏面包含了interface的一些關鍵信息,好比method的具體實現。

type itab struct {
  inter  *interfacetype   // 接口自身的元信息
  _type  *_type           // 具體類型的元信息
  link   *itab
  bad    int32
  hash   int32            // _type裏也有一個一樣的hash,此處多放一個是爲了方便運行接口斷言
  fun    [1]uintptr       // 函數指針,指向具體類型所實現的方法
}

其中值得注意的字段,我的理解以下:

  1. interface type包含了一些關於interface自己的信息,好比package path,包含的method。這裏的interfacetype是定義interface的一種抽象表示。
  2. type表示具體化的類型,與eface的 type類型相同。
  3. hash字段實際上是對_type.hash的拷貝,它會在interface的實例化時,用於快速判斷目標類型和接口中的類型是否一致。另,Go的interface的Duck-typing機制也是依賴這個字段來實現。
  4. fun字段實際上是一個動態大小的數組,雖然聲明時是固定大小爲1,但在使用時會直接經過fun指針獲取其中的數據,而且不會檢查數組的邊界,因此該數組中保存的元素數量是不肯定的。

因此,People擁有一個Show方法的,屬於非空接口,People的內部定義應該是一個iface結構體

type People interface {
    Show()  
}

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

stu是一個指向nil的空指針,可是最後return stu 會觸發匿名變量 People = stu值拷貝動做,因此最後live()放回給上層的是一個People insterface{}類型,也就是一個iface struct{}類型。 stu爲nil,只是iface中的data 爲nil而已。 可是iface struct{}自己並不爲nil.

因此以下判斷的結果爲BBBBBBB

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

(3) interface內部構造(空接口eface狀況)

下面代碼結果爲何?
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)
}

結果

non-empty interface

分析

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

在執行Foo(p)的時候,觸發x interface{} = p語句,因此此時 x結構以下。

因此 x 結構體自己不爲nil,而是data指針指向的p爲nil。


(4) inteface{}與*interface{}

ABCD中哪一行存在錯誤?
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
}

結果

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

看到這道題須要第一時間想到的是Golang是強類型語言,interface是全部golang類型的父類 函數中func f(x interface{})interface{}能夠支持傳入golang的任何類型,包括指針,可是函數func g(x *interface{})只能接受*interface{}

若是掌握interface構造,建議看下一篇文章
使用Golang的interface接口設計原則


關於做者:

mail: danbing.at@gmail.com
github: https://github.com/aceld
原創書籍gitbook: http://legacy.gitbook.com/@aceld

創做不易, 共同窗習進步, 歡迎關注做者, 回覆"zinx"有好禮

做者微信公衆號


文章推薦

開源軟件做品

(原創開源)Zinx-基於Golang輕量級服務器併發框架-完整版(附教程視頻)

(原創開源)Lars-基於C++負載均衡遠程調度系統-完整版

精選文章

典藏版-Golang調度器GMP原理與調度全分析

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

最經常使用的調試 golang 的 bug 以及性能問題的實踐方法?

Golang中的Defer必掌握的7知識點

Golang中的局部變量「什麼時候棧?什麼時候堆?」

使用Golang的interface接口設計原則

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

深刻淺出Golang的協程池設計

Go語言構建微服務一站式解決方案

相關文章
相關標籤/搜索