原創(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è)人理解如下:
-
interface type
包含了一些關(guān)于interface本身的信息,比如package path
捣炬,包含的method
慈格。這里的interfacetype是定義interface的一種抽象表示。 -
type
表示具體化的類型遥金,與eface的 type類型相同。 -
hash
字段其實(shí)是對(duì)_type.hash
的拷貝蒜田,它會(huì)在interface的實(shí)例化時(shí)稿械,用于快速判斷目標(biāo)類型和接口中的類型是否一致。另冲粤,Go的interface的Duck-typing機(jī)制也是依賴這個(gè)字段來實(shí)現(xiàn)美莫。 -
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ì)原則