欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

goalng?結構體?方法集?接口實例詳解

 更新時間:2022年09月16日 14:39:41   作者:lambdang  
這篇文章主要為大家介紹了goalng?結構體?方法集?接口實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

一 前序

很多時候我們以為自己懂了,但內心深處卻偶有困惑,知識是嚴謹?shù)?,偶有困惑就是不懂,很幸運通過大量代碼的磨練,終于看清困惑,并弄懂了。

本篇包括結構體,類型, 及 接口相關知識,希望對大家有所啟發(fā)。

二 事出有因

搞golang也有三四個年頭了,大小項目不少,各種golang書籍資料也閱無數(shù),今天突然被一個報錯搞懵了。演示代碼如下:

type MyErr struct{}
func(this MyErr)Error()string{
    return "myerr"
}
func main(){
    var Err *MyErr
    errors.As(MyErr{},Err) //這一句
}

errors.As是標準庫里的判斷錯誤類型的一個簡單函數(shù),按照如上寫法他運行報錯,報錯內容如下:

panic: errors: target must be a non-nil pointer
goroutine 1 [running]:
errors.As({0x107e280, 0x11523f8}, {0x104f3e0, 0x11523f8})
        D:/GO/src/errors/wrap.go:84 +0x3e5
github.com/pkg/errors.As(...)
        D:/GO/gopath/pkg/mod/github.com/pkg/errors@v0.9.1/go113.go:31
main.main()
        H:/information/demo1/main.go:19 +0x31
exit status 2

errors.As 方法簽名

func As(err error, target interface{}) bool

起初我沒有太關心報錯結果,我第一感覺是指針類型實現(xiàn)接口有問題,于是又改實現(xiàn)方法,又折騰變量,有時候ide提示方法未實現(xiàn),有時候運行報錯,偶有成功,為啥成功我也不知道。

突然我發(fā)現(xiàn)我對接口一直都停留在會用的基礎上,所有結構體方法接受者都用指針,所有結構體實例都用指針,一方面保證接口方法都能實現(xiàn),另一方面減少對象拷貝,減少內存用量。

于是帶著這個問題開始了刨根問題。在查閱資料中又發(fā)現(xiàn)了新的問題。

  • 指針方法集包括結構體所有方法,值方法集不包括指針方法集,為啥一個指針或者一個值實例可以調用所有方法。方法集的本質是啥?
type T struct{}
func (t T) Get() {
	fmt.Println("this is Get")
}
func (t *T) Set() {
	fmt.Println("this is set")
}
func main() {
	var a T
	a.Set()
	a.Get()
	(&a).Get()
	(&a).Set()
}
  • 為啥有時候指針對象無法調用非指針方法?如開始的err例子。
  • 嵌入類型的結構體,面對指針和值實例,方法集規(guī)律是啥?
  • 接口到底是啥?nil又是啥?
  • 結構體體結構到底是怎么樣的?
  • 實例結構又如何?怎么通過實例找到相應的方法?
  • 。。。

三 結構體與實例的數(shù)據結構

1. 結構體類型

結構體就是一個模板,用于生成實例用的,包括最基本的屬性集,值的方法集,指針方法集。

type T struct{
    Num int
}
func (t T) Get() int{
	fmt.Println("this is Get")
        return t.Num
}
func (t *T) Set(i int) {
	fmt.Println("this is set")
        t.Num = i
}

這就是一個定義的結構體。

func (t T) Get() 該方法的接受者 t是一個實例值,所以該方法稱為值方法。

func (t *T) Set() 該方法的接受者 t 是一個指針,所以該方法成為指針方法。

2. 實例

實例就是結構體實例化后的變量,用T類型說明。

    var a T
    var b *T
    var c = T{1}
    var d = &T{1}

這四種實例定義發(fā)生了什么?數(shù)據結構如何?

實例數(shù)據結構主要包括三部分。

  • 頭部信息,說明實例大小,實例是指針還是非指針等
  • 值,指針時候是指向實例的地址,非指針時候是具體的屬性值
  • 類型

實例a是一個空結構體實例,其特點是a雖然沒有顯示賦值,但是會默認創(chuàng)建一個a實例,其中的屬性都是"類型零值"。

實例b是一個指針類型,特點是沒有被初始化,指針未任何實例。

實例c是一個顯示賦值的實例,和a區(qū)別就是Num初始化值不再是"類型零值",而是1。

實例d就有點復雜了,他會有個實例及指針兩種數(shù)據,指針指向實例。實例初始化非"類型零值"。

關于圖中地址的說明,所有數(shù)據結構最終都是內存中的一段連續(xù)代碼,都有開始地址,其他需要使用該數(shù)據的地方都是通過該地址找到這段內存信息的。當然要說到代碼,內存,虛擬地址,堆棧,程序運行,會有很多內容,這里只要知道通過地址能找到該數(shù)據信息即可。

注意,上圖也僅僅只是示意圖,幫助理解。其中類型指針實現(xiàn)并不是一個真的指針而是一個關于類型元信息的偏移地址。

3 方法調用

結合上面的圖,說一下方法調用問題。為啥值方法和指針方法都可以調用所有方法,并且都能成功,并且修改都可以成功。

	a.Get()
	a.Set(2)
	// b.Get() 編譯器通過 運行不通過
	b.Set(3)
	c.Get()
	c.Set(4)
	d.Get()
	d.Set(5)

3.1 方法表達式

實例的方法調用的本質是函數(shù),類似python,編譯器調用該函數(shù)時候默認的第一個參數(shù)是實例值或者實例指針。

T.Get(a)

(*T).Set(b,2)

通過類型直接調用類型中的函數(shù),這就是方法表達式調用。真實的實例調用,也是通過找到類型并調用類型的方法。關于"方法表達式"這個詞出自《go語言核心編程》第三章,類型系統(tǒng),有興趣的可以看看。

方法表達式有個特點,就是不會被自動轉換,通過方法表達式可以清楚知道值方法集或指針方法集是否有該方法。

在沒有說到接口之前,判斷一個方法是否屬于方法集用這個方法表達式是比較方便的。

3.2 值實例調用所有方法

a和c本質是一樣的,只是初始值不一樣。拿c做例子進行講解。

c.Get() == T.Get(a)

上邊代碼這個不用解釋太多,就是c實例通過類型信息找到相關的值方法進行調用。

c.Set() == (&c).Set(4) == (*T).Set(c,4)

上邊代碼 c中對應的T中方法并不包含Set方法。

T.Set() 你會發(fā)現(xiàn)編譯器會報錯 T中沒有Set方法

但*T中有方法Set,這時候編譯器會生成一個*c,指針對象,在通過該對象調用Set方法。雖然通過指針對象調用Set但確實把c對象中的Num修改成功了,因為指針指向的正是c實例。如下圖:

這就是為啥實例方法集中沒有Set方法,也可以調用Set方法,編譯器進行了自動轉換,而這樣設計是合理的,通過Set操作,c實例中的Num確實變成4,符合預期。

3.3 指針實例調用所有方法

b和d都是指針實例,看看上圖關于b和d的數(shù)據結構示意圖,這兩個圖里最大的區(qū)別就是有沒有匿名實例,b因為是空指針沒有指向任何實例,所以只有類型信息。

編譯器知道你是個指針,查看類型中的所有方法,包括值方法和指針方法,有Set和Get所以編譯通過,但是在運行的時候,因為是空指針,無法找到值的方法Get,所以運行時候報錯 panic: errors: target must be a non-nil pointer

d因為指向一個實例,所以順著這個實例找到Get方法進行調用,這都是編譯器自動進行的。

d.Get() == (&d).Get() == (T).Get(*d)

通用使用方法表達式,也可以知道指針方法集中是沒有Get方法的。

(*T).Get() 編譯器不會通過 說明指針方法集中確實沒有Get函數(shù) 所以只能通過轉化成實例來調動Get方法

這種自動轉化及操作的結果也是符合預期的,拿到了d指針指向的實例的數(shù)據。

3.4 空指針無法調用值方法

在回過頭看最初的err問題,原因就出在給了一個空指針,要通過一個空指針找到一個值方法,但是運行時候無法找到,所以panic了

四 接口

正常情況下,值實例還是指針實例都可以調用所有方法,并且修改都可以成功,那為什么要區(qū)分值的方法集和指針的方法集,這就不得不提接口。

方法集是給接口準備。

方法集是"符合預期"的。

可以說因為接口的需要才會有方法集概念,只有接口中的方法與方法集中的方法相匹配時候,該方法集的實例才是該接口的實現(xiàn)實例。

可是問題又來了,明明一個實例對象不管是指針還是非指針實例都可以執(zhí)行全部的方法,技術上完全可以實現(xiàn),為什么還要區(qū)分指針非指針方法?這是因為"不符合預期",為什么,為什么"不符合預期",看下邊解釋。

1 接口數(shù)據結構

要說明白接口和方法集的關系不是一件容易的事,先從接口結構說起。

接口類型跟struct類型不同,字面上看,接口只有方法頭,沒有屬性。

接口實例跟一般的struct實例也不一樣,它是一種動態(tài)的實例,只有接口實例被具體實例(值或指針)賦值的時候,接口實例才能確定。如下圖。

接口實例跟結構體實例類似,也包括兩部分,值和類型。

接口中的值是動態(tài)的,當被具體結構體實例賦值時候才能確定該值。該值就是結構體實例的值的拷貝,當實例是非指針時候會把數(shù)據都拷貝過來,當是實例是指針時候會把指針拷貝過來。golang中一切賦值都是拷貝,包括接口賦值,也是因為拷貝才會有很多"不符合預期的"結果。

接口中的類型包括動態(tài)類型和自身的接口類型,自身類型沒啥好說的,看上圖就明白了,主要是動態(tài)類型,這個是存儲了當前賦值的結構體實例的類型。

2 接口賦值

以下面的接口賦值代碼進行說明解釋。

package main
type I interface {
	Get() int
	Set(i int)
}
type T struct {
	Num int
}
func (t T) Get() int {
	return t.Num
}
func (t *T) Set(num int) {
	t.Num = num
}
func main() {
	var a T
	var b *T
	var c = T{}
	var d = &T{}
	var ia I = a //編譯不通過 方法集不匹配
	var ib I = b //編譯通過 運行會報錯 panic: runtime error: invalid memory address or nil pointer dereference
	var ic I = c //編譯不通過 方法集不匹配
	var id I = d
}

例子代碼很簡單,就是一個接口類型I,一個struct類型T,其實現(xiàn)了值Get方法,指針Set方法。

上邊代碼中a,b,c,d已經在上部分進行過講解了。

ia,ib,ic,id賦值過程如下圖:

值方法集

ia,ic接口對象其實在編輯階段IDE就會給出報錯提示,實例和接口不匹配,因為a和c實例方法集中只有一個Get函數(shù),可以通過前邊提到的"表達式方法"進行驗證,這里通過IDE提示也知道缺少Set函數(shù)。

那么問題來了,在第一部分單獨a,c對象是可以調用所有方法,這里接口實現(xiàn)為啥要弄出個方法集進行限制?因為"拷貝"和"不符合預期"。

假設a,c可以成功賦值給接口ia,ic,賦值后a,c中的數(shù)據會拷貝到接口的動態(tài)值區(qū)域,要是成功執(zhí)行了Set函數(shù),將接口動態(tài)值區(qū)域的數(shù)據進行了修改,那原來的a,c中的數(shù)據并未改變,這個是"不符合預期的"。所以干脆就不允許這么操作。

更常用的"不符合預期"解釋代碼是當接口是參數(shù)值時候。如下代碼。

func DoT(a I) {
	a.Set(11)
}
func main(){
    ...
    DoT(ic)
    fmt.Println(ic.Get())
}

DoT函數(shù)用I做參數(shù),內部對I進行了操作,用ic或者ia做參數(shù),如果可以成功,最后打印ic或者ia中的值,并未改變,這不符合預期,很令人困惑。這段原理可參考<<go核心編程>>第三章類型系統(tǒng)相關描述。

指針方法集

ib和id都是指針類型,其方法集包括所有方法,即Get和Set,其中Get是通過編譯器自動轉化進行間接調用,值實例不允許調用指針實例的方法集是因為"不符合預期",那指針實例就允許調用值實例的方法了?是的,允許,因為"符合預期"。

還用下面的代碼做解釋。

func DoT(a I) {
    a.Set(a.Get()++)
}
func main(){
    ...
    DoT(id)
    fmt.Println(id.Get())
}

這里用id做參數(shù),最終執(zhí)行完,結果id確實增加了1,符合預期。

結合前邊接口賦值的圖進行分析,接口動態(tài)值區(qū)域拷貝了一份id的指針值,這個指針指向一個具體的實例。如下圖。

從這里可以看出對id的任何操作其實都是對具體的實例進行的操作,所以無論讀寫都是符合預期的,所以當使用指針調用Get方法時候就會進行自動轉化調用值的Get方法。

至于ib為啥編譯通過,運行時候就報錯,也是因為指針是個nil值,無法自動轉化找到Get方法。

總結

翻了好幾天資料,本來想把嵌入類型和反射都寫進來,但是時間有點倉促,大家可以結合上邊的講解,自行對嵌入類型和反射進行研究,基礎原理都一樣。

這里總結一下:

實例都包括兩部分,值和類型,編譯器正是通過實例類型所以才知道了其方法集。

單獨實例使用時候,是允許調用所有方法的,調用非自身方法集時候編譯器會自動進行轉換,并且都會調用成功,符合預期。

實例賦值給接口時候,是把實例信息拷貝到接口中的,其數(shù)據結構和原來實例完全不一樣了,同時接口會嚴格檢查方法集,以防止不符合預期行為發(fā)生。

實例是指針時候,并且為空的時候,并且包含非指針方法時候,無論是該實例的接口還是該實例,都不能進行任何方法調用,否則會有運行時panic發(fā)生。未指向任何具體數(shù)據變量,無論讀寫肯定報錯。

接口斷言知道為啥一定要是接口才能進行斷言吧,因為接口的動態(tài)值和動態(tài)類型要進行動態(tài)填充,接口斷言也可以判斷一個實例的方法集,而且是安全的判斷

_,ok:=interface{}(a).(I)

判斷一個實例是否有哪個方法,方法集中的方法有哪些,目前看可以通過三種方法"方法表達式"","接口賦值","接口斷言"。

其實還有好多知識點比如nil類型,空接口,空指針,相互比較時候真假結果,嵌入結構體方法集,反射操作,等等,只要把原理搞清了都很容易理解的。

以上就是goalng 結構體 方法集 接口實例詳解的詳細內容,更多關于goalng 結構體 方法集 接口的資料請關注腳本之家其它相關文章!

相關文章

  • Go1.18新特性之泛型使用三步曲(小結)

    Go1.18新特性之泛型使用三步曲(小結)

    本文主要介紹了Go1.18新特性之泛型,是Go1.18的新特性,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-04-04
  • 深入探究Golang中flag標準庫的使用

    深入探究Golang中flag標準庫的使用

    在本文中,我們將深入探討 flag 標準庫的實現(xiàn)原理和使用技巧,以幫助讀者更好地理解和掌握該庫的使用方法,文中的示例代碼講解詳細,感興趣的可以了解一下
    2023-04-04
  • Go語言開發(fā)框架反射機制及常見函數(shù)示例詳解

    Go語言開發(fā)框架反射機制及常見函數(shù)示例詳解

    這篇文章主要為大家介紹了Go語言開發(fā)框架反射機制及常見函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • golang post請求常用的幾種方式小結

    golang post請求常用的幾種方式小結

    這篇文章主要介紹了golang post請求常用的幾種方式小結,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 使用Golang的singleflight防止緩存擊穿的方法

    使用Golang的singleflight防止緩存擊穿的方法

    這篇文章主要介紹了使用Golang的singleflight防止緩存擊穿的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-04-04
  • golang執(zhí)行命令獲取執(zhí)行結果狀態(tài)(推薦)

    golang執(zhí)行命令獲取執(zhí)行結果狀態(tài)(推薦)

    這篇文章主要介紹了golang執(zhí)行命令獲取執(zhí)行結果狀態(tài)的相關知識,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2019-11-11
  • Go語言常見錯誤接口污染解決分析

    Go語言常見錯誤接口污染解決分析

    這篇文章主要為大家介紹了Go語言常見錯誤接口污染解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • 解決Golang并發(fā)工具Singleflight的問題

    解決Golang并發(fā)工具Singleflight的問題

    前段時間在一個項目里使用到了分布式鎖進行共享資源的訪問限制,后來了解到Golang里還能夠使用singleflight對共享資源的訪問做限制,于是利用空余時間了解,將知識沉淀下來,并做分享
    2022-05-05
  • Go語言的變量定義詳情

    Go語言的變量定義詳情

    這篇文章主要介紹了Go語言的變量定義詳情,go定義變量的方式和c,c++,java語法不一樣,var?變量名類型,var在前,變量名在中間,類型在后面,下文更多詳細內容需要的小伙伴可以參考一下
    2022-03-03
  • Golang捕獲panic堆棧信息的講解

    Golang捕獲panic堆棧信息的講解

    今天小編就為大家分享一篇關于Golang捕獲panic堆棧信息的講解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04

最新評論