天天看點

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

本篇通過一些面試真題,來分析interface的幾點注意及内部底層結構,

提綱如下:

一、

interface的指派問題

二、

interface的内部結構(iface與eface)

三、

interface{} 與 *interface{}

一、interface的指派問題

01

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

代碼
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))
}           

複制

02

分析與說明

繼承與多态的特點,在golang中對多态的特點展現從文法上并不是很明顯。

我們知道發生多态的幾個要素:

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

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

3、有父類指針指向子類的具體對象

那麼,滿足上述3個條件,就可以産生多态效果,就是,父類指針可以調用子類的具體方法。

是以上述代碼報錯的地方在

var peo People = Stduent{}

這條語句,

Student{}

已經重寫了父類

People{}

中的

Speak(string) string

方法,那麼隻需要用父類指針指向子類對象即可。

是以應該改成

var peo People = &Student{}

即可編譯通過。(People為interface類型,就是指針類型)

二、interface的内部構造(非空接口iface情況)

01

以下代碼列印出來什麼内容,說出為什麼?

代碼
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           

複制

02

分析

我們需要了解

interface

的内部結構,才能了解這個題目的含義。

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

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

var MyInterface interface{}           

複制

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

type MyInterface interface {
        function()
}           

複制

這兩種interface類型分别用兩種

struct

表示,空接口為

eface

, 非空接口為

iface

.

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

03

空接口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*

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

04

非空接口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指針擷取其中的資料,并且不會檢查數組的邊界,是以該數組中儲存的元素數量是不确定的。
Golang中interface内部構造與面試真題分析

是以,People擁有一個Show方法的,屬于非空接口,People的内部定義應該是一個

iface

結構體

type People interface {
    Show()
}           

複制

Golang中interface内部構造與面試真題分析
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.

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

是以如下判斷的結果為

BBBBBBB

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

複制

三、interface的内部構造(空接口eface情況)

01

下面代碼的結果為什麼?

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           

複制

02

分析

不難看出,

Foo()

的形參

x interface{}

是一個空接口類型

eface struct{}

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

在執行

Foo(p)

的時候,觸發

x interface{} = p

語句,是以此時 x結構如下。

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

是以 x 結構體本身不為nil,而是data指針指向的p為nil。

四、interface{} 與 *interface{}

01

ABCD哪一行是錯誤的?

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
}           

複制

02

結果

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{}

感謝觀看!