本篇通過一些面試真題,來分析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
.
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*
。
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 // 函數指針,指向具體類型所實作的方法
}
複制
其中值得注意的字段,個人了解如下:
-
包含了一些關于interface本身的資訊,比如interface type
,包含的package path
。這裡的interfacetype是定義interface的一種抽象表示。method
-
表示具體化的類型,與eface的 type類型相同。type
-
字段其實是對hash
的拷貝,它會在interface的執行個體化時,用于快速判斷目标類型和接口中的類型是否一緻。另,Go的interface的Duck-typing機制也是依賴這個字段來實作。_type.hash
-
字段其實是一個動态大小的數組,雖然聲明時是固定大小為1,但在使用時會直接通過fun指針擷取其中的資料,并且不會檢查數組的邊界,是以該數組中儲存的元素數量是不确定的。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")
}
}
複制
三、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{}
。
在執行
Foo(p)
的時候,觸發
x interface{} = p
語句,是以此時 x結構如下。
是以 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{}
感謝觀看!