<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么 Go 不支持 []T 轉換為 []interface

          共 11533字,需瀏覽 24分鐘

           ·

          2023-02-04 16:48

          在 Go 中,如果 interface{} 作為函數(shù)參數(shù)的話,是可以傳任意參數(shù)的,然后通過類型斷言來轉換。

          舉個例子:

          package main

          import "fmt"

          func foo(v interface{}) {
              if v1, ok1 := v.(string); ok1 {
                  fmt.Println(v1)
              } else if v2, ok2 := v.(int); ok2 {
                  fmt.Println(v2)
              }
          }

          func main() {
              foo(233)
              foo("666")
          }

          不管是傳 int 還是 string,最終都能輸出正確結果。

          那么,既然是這樣的話,我就有一個疑問了,拿出我舉一反三的能力。是否可以將 []T 轉換為 []interface 呢?

          比如下面這段代碼:

          func foo([]interface{}) { /* do something */ }

          func main() {
              var a []string = []string{"hello""world"}
              foo(a)
          }

          很遺憾,這段代碼是不能編譯通過的,如果想直接通過 b := []interface{}(a) 的方式來轉換,還是會報錯:

          cannot use a (type []string) as type []interface {} in function argument

          正確的轉換方式需要這樣寫:

          b := make([]interface{}, len(a), len(a))
          for i := range a {
              b[i] = a[i]
          }

          本來一行代碼就能搞定的事情,卻非要讓人寫四行,是不是感覺很麻煩?那為什么 Go 不支持呢?我們接著往下看。

          官方解釋

          這個問題在官方 Wiki 中是有回答的,我復制出來放在下面:

          The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear. Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time. Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long. This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long. The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.

          大概意思就是說,主要有兩方面原因:

          1. []interface{} 類型并不是 interface,它是一個切片,只不過碰巧它的元素是 interface;
          2. []interface{} 是有特殊內存布局的,跟 interface 不一樣。

          下面就來詳細說說,是怎么個不一樣。

          內存布局

          首先來看看 slice 在內存中是如何存儲的。在源碼中,它是這樣定義的:

          // src/runtime/slice.go

          type slice struct {
              array unsafe.Pointer
              len   int
              cap   int
          }
          • array 是指向底層數(shù)組的指針;
          • len 是切片的長度;
          • cap 是切片的容量,也就是 array 數(shù)組的大小。

          舉個例子,創(chuàng)建如下一個切片:

          is := []int64{0x550x220xab0x9}

          那么它的布局如下圖所示:

          假設程序運行在 64 位的機器上,那么每個「正方形」所占空間是 8 bytes。上圖中的 ptr 所指向的底層數(shù)組占用空間就是 4 個「正方形」,也就是 32 bytes。

          接下來再看看 []interface{} 在內存中是什么樣的。

          回答這個問題之前先看一下 interface{} 的結構,Go 中的接口類型分成兩類:

          1. iface 表示包含方法的接口;
          2. eface 表示不包含方法的空接口。

          源碼中的定義分別如下:

          type iface struct {
              tab  *itab
              data unsafe.Pointer
          }
          type eface struct {
              _type *_type
              data  unsafe.Pointer
          }

          具體細節(jié)我們不去深究,但可以明確的是,每個 interface{} 包含兩個指針, 會占據(jù)兩個「正方形」。第一個指針指向 itab 或者 _type;第二個指針指向實際的數(shù)據(jù)。

          所以它在內存中的布局如下圖所示:

          因此,不能直接將 []int64 直接傳給 []interface{}。

          程序運行中的內存布局

          接下來換一個更形象的方式,從程序實際運行過程中,看看內存的分布是怎么樣的?

          看下面這樣一段代碼:

          package main

          var sum int64

          func addUpDirect(s []int64) {
           for i := 0; i < len(s); i++ {
            sum += s[i]
           }
          }

          func addUpViaInterface(s []interface{}) {
           for i := 0; i < len(s); i++ {
            sum += s[i].(int64)
           }
          }

          func main() {
           is := []int64{0x550x220xab0x9}

           addUpDirect(is)

           iis := make([]interface{}, len(is))
           for i := 0; i < len(is); i++ {
            iis[i] = is[i]
           }

           addUpViaInterface(iis)
          }

          我們使用 Delve 來進行調試,可以點擊這里進行安裝。

          dlv debug slice-layout.go
          Type 'help' for list of commands.
          (dlv) break slice-layout.go:27
          Breakpoint 1 set at 0x105a3fe for main.main() ./slice-layout.go:27
          (dlv) c
          > main.main() ./slice-layout.go:27 (hits goroutine(1):1 total:1) (PC: 0x105a3fe)
              22:  iis := make([]interface{}, len(is))
              23:  for i := 0; i < len(is); i++ {
              24:   iis[i] = is[i]
              25:  }
              26:
          =>  27:  addUpViaInterface(iis)
              28: }

          打印 is 的地址:

          (dlv) p &is
          (*[]int64)(0xc00003a740)

          接下來看看 slice 在內存中都包含了哪些內容:

          (dlv) x -fmt hex -len 32 0xc00003a740
          0xc00003a740:   0x10   0xa7   0x03   0x00   0xc0   0x00   0x00   0x00
          0xc00003a748:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a750:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a758:   0x00   0x00   0x09   0x00   0xc0   0x00   0x00   0x00

          每行有 8 個字節(jié),也就是上文說的一個「正方形」。第一行是指向數(shù)據(jù)的地址;第二行是 4,表示切片長度;第三行也是 4,表示切片容量。

          再來看看指向的數(shù)據(jù)到底是怎么存的:

          (dlv) x -fmt hex -len 32 0xc00003a710
          0xc00003a710:   0x55   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a718:   0x22   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a720:   0xab   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a728:   0x09   0x00   0x00   0x00   0x00   0x00   0x00   0x00

          這就是一片連續(xù)的存儲空間,保存著實際數(shù)據(jù)。

          接下來用同樣的方式,再來看看 iis 的內存布局。

          (dlv) p &iis
          (*[]interface {})(0xc00003a758)
          (dlv) x -fmt hex -len 32 0xc00003a758
          0xc00003a758:   0x00   0x00   0x09   0x00   0xc0   0x00   0x00   0x00
          0xc00003a760:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a768:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          0xc00003a770:   0xd0   0xa7   0x03   0x00   0xc0   0x00   0x00   0x00

          切片的布局和 is 是一樣的,主要的不同是所指向的數(shù)據(jù):

          (dlv) x -fmt hex -len 64 0xc000090000
          0xc000090000:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
          0xc000090008:   0xa8   0xee   0x0a   0x01   0x00   0x00   0x00   0x00
          0xc000090010:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
          0xc000090018:   0x10   0xed   0x0a   0x01   0x00   0x00   0x00   0x00
          0xc000090020:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
          0xc000090028:   0x58   0xf1   0x0a   0x01   0x00   0x00   0x00   0x00
          0xc000090030:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
          0xc000090038:   0x48   0xec   0x0a   0x01   0x00   0x00   0x00   0x00

          仔細觀察上面的數(shù)據(jù),偶數(shù)行內容都是相同的,這個是 interface{}itab 地址。奇數(shù)行內容是不同的,指向實際的數(shù)據(jù)。

          打印地址內容:

          (dlv) x -fmt hex -len 8 0x010aeea8
          0x10aeea8:   0x55   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          (dlv) x -fmt hex -len 8 0x010aed10
          0x10aed10:   0x22   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          (dlv) x -fmt hex -len 8 0x010af158
          0x10af158:   0xab   0x00   0x00   0x00   0x00   0x00   0x00   0x00
          (dlv) x -fmt hex -len 8 0x010aec48
          0x10aec48:   0x09   0x00   0x00   0x00   0x00   0x00   0x00   0x00

          很明顯,通過打印程序運行中的狀態(tài),和我們的理論分析是一致的。

          通用方法

          通過以上分析,我們知道了不能轉換的原因,那有沒有一個通用方法呢?因為我實在是不想每次多寫那幾行代碼。

          也是有的,用反射 reflect,但是缺點也很明顯,效率會差一些,不建議使用。

          func InterfaceSlice(slice interface{}) []interface{} {
           s := reflect.ValueOf(slice)
           if s.Kind() != reflect.Slice {
            panic("InterfaceSlice() given a non-slice type")
           }

           // Keep the distinction between nil and empty slice input
           if s.IsNil() {
            return nil
           }

           ret := make([]interface{}, s.Len())

           for i := 0; i < s.Len(); i++ {
            ret[i] = s.Index(i).Interface()
           }

           return ret
          }

          還有其他方式嗎?答案就是 Go 1.18 支持的泛型,這里就不過多介紹了,大家有興趣的話可以繼續(xù)研究。

          以上就是本文的全部內容,如果覺得還不錯的話歡迎點贊,轉發(fā)關注,感謝支持。


          參考文章:

          • https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces
          • https://github.com/golang/go/wiki/InterfaceSlice
          • https://eli.thegreenplace.net/2021/go-internals-invariance-and-memory-layout-of-slices/


          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關注公眾號 「polarisxu」,回復 ebook 獲??;還可以回復「進群」,和數(shù)萬 Gopher 交流學習。

          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人免费高清视频 | 伊人天天操 | 久草福利资源网 | 人妻少妇嫩草AV无码 | 精品国产999 |