在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    Go小知識新解

    Go接口實現中的的值接收者指針接收者

    1、值接收者和指針接收者

    所謂指針接收者和值接收者這兩個概念,用GO寫了一陣子代碼的人都了解了,這里只做簡要說明一下,也就是對于一個給定結構,咱們對結構進行方法包裝的時候,固定必傳的參數,用來指向這個對象結構自身的一個參數,在go中也就是形式如下:

    type testStruct struct{a int}func (a testStruct)sum(x,y int)int { return a.a + x + y}func (a *testStruct)modify(v int) { a.a = v}

    我們對結構體testStruct進行了包裝,提供了兩個方法,sum和modify,其中sum的方法接收者為a testStruct,這個就是值接收者,而modify的接收者為a *testStruct就是指針接收者,也就是說固定對象指針,一個傳遞的是指針地址,而另外一個直接傳遞的是結構值拷貝了

    對指針有一定了解的,都可以知道,指針傳遞過去的,可以直接修改結構內部內容,而值傳遞過去的,無論如何修改這個接收者的數據,不會對原對象結構產生影響。而對于咱們包裝結構對象的時候,到底是使用指針還是使用值接收者,這個實際上沒有太大的定論,就我個人的觀點來說,如果結構體占有的內存空間不大(<KB級別),而又不需要修改內部的,同時結構對象內部沒有同步對象比如(sync包中的mutex,rwlock,waitGroup等之類的結構的話,可以直接值傳遞,實際上值copy也沒有咱們想象的那么慢,很多時候,都用指針,最后的GC回收掃描可能都比咱們這個傳遞copy的消耗大)

    2、實現接口的值接收者和指針接收者有啥區(qū)別

    也就是比如定義如下

    type ITest interface { sum1(int, int) int}type ITest2 interface { sum2(int, int) int}type testStruct struct { a int}func (t testStruct) sum1(x, y int) int { return t.a + x + y}func (t *testStruct) sum2(x, y int) int { return t.a + x + y}

    這里面的值接收者和指針接收者有什么區(qū)別,這里咱來寫一個測試

    func main() { t := testStruct{ a: 3, } var test1 ITest var test2 ITest2 test2 = &t if v, ok := test2.(ITest); ok { fmt.Println(“指針接收者接口->值接收者接口”, v.sum1(5, 5)) } else { fmt.Println(“指針接收者接口 無法轉到 值接收者接口”) } test1 = t if v, ok := test1.(ITest2); ok { fmt.Println(“值接收者接口->指針接收者接口”, v.sum2(3, 3)) } else { fmt.Println(“值接收者接口 無法轉到 指針接收者接口”) }}

    通過這個測試用例可以發(fā)現,指針接收者實現的接口可以同時支持轉移到值接收者接口和指針接收者接口,而用值接收者實現的接口,則無法轉移到使用指針接收者實現的接口,為啥子呢?目前網上或者各類資料上都是給的一個很官方很官方,而且很書面話難以理解的說明,大致意思如下:

    • 當方法的接收者定義為值類型時, Go 語言編譯器會自動做轉換,所以值類型接收者和指針類型接收者是等價的,編譯不會報錯,運行也都可以調用相應方法
    • 在實現接口時,應保持接收者定義、結構體定義、斷言類型一致

    這是目前網絡或者各種資料上都是差不多是這樣說的,看似講了,實際上就說了一個結果,根本就沒說出來一個為什么。這樣的總結出來,一個初學者的角度來看,是很不好理解的,初學者要么就是死記硬背,要么就是生搬硬套,甚至直到寫了好多好多代碼了,都還沒有搞明白一個為啥子,只是會用了而已,從長遠來說這是不利于自身提高的。

    說了這么多,那么這到底是個什么原因呢,其實很簡單,我們關注其本質就行了:

    • 值接收者是傳遞的時候,實際上是執(zhí)行了一個值拷貝傳遞進去了,這個值拷貝和原數據已經沒有任何關系了
    • 指針接收者傳遞的是地址,此時和原數據還有關聯(lián),通過解指針操作可以到原數據的數據空間

    有這兩個本質點,咱們自己來思考一下,如果你來實現這個編譯器的時候,用指針接收的時候,指針接收者,默認就能直接獲取支持,而值接收者實現接口的咱們可以直接來一個解指針就變成了值,就能匹配上值接收者實現的接口了,反過來說,如果值接收者,此時要匹配指針接收者,如何匹配呢,取一個地址就變成了指針了,此時數據類型確實是匹配了,但是,地址指向的數據區(qū)不對了,因為我們剛剛說了值接收者拷貝了一個新值之后是完全的一個新的對象,這個新對象和原始對象一點關系都沒有,咱們取地址,取的也是這個新對象地址,對這個地址進行操作,也是這個新對象的內部數據,和原始數據內部沒有任何關系,所以由此就能推斷出,這個是為啥子值接收者不能匹配上指針接收者,而指針接收者卻可以匹配上值接收者了。

    GO字符串小計

    1、在某個作用域內部,所有定義的字符串的數據區(qū)相同

    這個很好驗證,代碼如下:

    func main(){ tstr := “test1” data := (*reflect.StringHeader)(unsafe.Pointer(&tstr)) fmt.Println(“地址:”, data.Data) tstr2 := “test1” data = (*reflect.StringHeader)(unsafe.Pointer(&tstr2)) fmt.Println(“地址:”, data.Data)}

    2、字符串相加會產生一個新串

    這個也很好驗證

    3、字符串真的是不可變的嗎

    實際上從字符串的結構

    type stringStruct struct{Data uintptrlen int}

    從這個結構,就能大致的推斷出來,字符串設計成這樣就不具備直接擴容+來增加新數據,而如果咱們直接使用string[index] = ‘a’,用這種方式,就不能編譯通過,官方也確定說字符串是不可變的。那么真的是不可變的嗎?

    通過上面的結構,在加上go的slice切片的數據結構

    type sliceStruct struct{Data uintptrlen intcap int}

    由此可見,咱們可以將字符串通過指針方式強轉為一個byte數組指針,然后通過byte切片來修改,試試

    func main() { tstr := “test1” data := (*reflect.StringHeader)(unsafe.Pointer(&tstr)) btHeader := reflect.SliceHeader{ Data: data.Data, Len: data.Len, Cap: data.Len, } bt := *(*[]byte)(unsafe.Pointer(&btHeader)) bt[0] = ‘a’}

    編譯通過,運行報錯

    unexpected fault address 0xae2e27fatal error: fault

    這個錯誤,基本上就是一個內存的保護錯誤,是寫異常,所以說明了,這個肯定做了內存寫保護,那么直接修改一下內存區(qū)的屬性,去掉他的寫保護,就能寫了

    以下代碼都是在Win平臺,Go1.18,Win上修改內存權限屬性,使用VirtualProtect,代碼如下

    func main() { tstr := “test1” data := (*reflect.StringHeader)(unsafe.Pointer(&tstr)) btHeader := reflect.SliceHeader{ Data: data.Data, Len: data.Len, Cap: data.Len, } bt := *(*[]byte)(unsafe.Pointer(&btHeader)) kernelDell := syscall.NewLazyDLL(“kernel32.dll”) kernelDell.Load() VirtualProtect := kernelDell.NewProc(“VirtualProtect”) var old1 uint32 VirtualProtect.Call(uintptr(unsafe.Pointer(data.Data)), uintptr(data.Len), 0x40, uintptr(unsafe.Pointer(&old1))) bt[0] = ‘a’ bt[1] = ‘a’ fmt.Println(tstr)}

    此時運行,就能發(fā)現tstr的內容被咱們變了,這種情況實際上在實際開發(fā)中不具有實際意義,因為本身在語言層面,已經做了層層限制,咱們這是屬于非法強制的操作方式,是流氓行為,那么是否有比較溫和一點的操作方式呢?答案是有的,且往下看。

    通過上面,我們已經用到了字符串結構,切片結構,要想字符串內容可變,那么咱們自己構造字符串的數據內容區(qū)域,且讓這個數據區(qū)木有內存寫保護不就行了,內容區(qū)可變,GO原生態(tài)的byte數組不就行嘛,所以咱們自己構造一下

    func main() { buffer := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0} stringData := reflect.StringHeader{ Data: uintptr(unsafe.Pointer(&buffer[0])), Len: len(buffer),} buffer[0] = ‘h’ buffer[1] = ‘e’ buffer[2] = ‘l’ buffer[3] = ‘l’ buffer[4] = ‘o’ buffer[5] = ‘ ‘ buffer[6] = ‘w’ buffer[7] = ‘o’ buffer[8] = ‘r’ str := *(*string)(unsafe.Pointer(&stringData)) fmt.Println(str)}

    此時我們直接修改buffer的內容,就是直接修改了str的數據內容了。而又不會像前面的一樣遇到內存寫保護

    4、字符串轉換優(yōu)化時可能碰到的坑

    通過前面討論的字符串的可變性的方法,咱們可以知道,很多時候,[]byte到字符串的轉變,可以直接構造其結構,而共享數據,從而達到減少數據內存copy的方式來進行優(yōu)化,再使用這些優(yōu)化的時候,一定需要注意,字符串或者數組的生命周期,是否會存在被改寫的情況,從而導致前后不一致的問題。

    比如下面這段代碼:

    func main() { buffer := []byte(“test”) stringData := reflect.StringHeader{ Data: uintptr(unsafe.Pointer(&buffer[0])), Len: len(buffer), } str := *(*string)(unsafe.Pointer(&stringData)) mmp := make(map[string]int, 32) mmp[str] = 3 mmp[“abcd”] = 4 fmt.Println(mmp[str]) buffer[0] = ‘a’ buffer[1] = ‘b’ buffer[2] = ‘c’ buffer[3] = ‘d’ fmt.Println(mmp[str]) fmt.Println(mmp[“test”]) fmt.Println(mmp[“abcd”]) for k, v := range mmp { fmt.Println(k, v) }}

    大家可以猜想一下,這個最后里面的數據mmp中,”test”的value是多少,”abcd”的value是多少,然后想想為什么,且等端午之后,再來分解

    鄭重聲明:本文內容及圖片均整理自互聯(lián)網,不代表本站立場,版權歸原作者所有,如有侵權請聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年7月1日 02:45
    下一篇 2022年7月1日 02:45

    相關推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息