<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>

          一篇理解什么是CanSet, CanAddr?

          共 5635字,需瀏覽 12分鐘

           ·

          2020-11-24 02:33

          文章有點長,主要解答問題,何時reflect.Value.CanSet 返回true,何時返回false。如果已理解,請直接繞開文字收圖點贊。

          什么是可設置( CanSet )

          首先需要先明確下,可設置是針對 reflect.Value 的。普通的變量要轉變成為 reflect.Value 需要先使用 reflect.ValueOf() 來進行轉化。

          那么為什么要有這么一個“可設置”的方法呢?比如下面這個例子:

          var?x?float64?=?3.4
          v?:=?reflect.ValueOf(x)
          fmt.Println(v.CanSet())?//?false

          golang 里面的所有函數(shù)調(diào)用都是值復制,所以這里在調(diào)用 reflect.ValueOf 的時候,已經(jīng)復制了一個 x 傳遞進去了,這里獲取到的 v 是一個 x 復制體的 value。那么這個時候,我們就希望知道我能不能通過 v 來設置這里的 x 變量。就需要有個方法來輔助我們做這個事情:CanSet()

          但是, 非常明顯,由于我們傳遞的是 x 的一個復制,所以這里根本無法改變 x 的值。這里顯示的就是 false。

          那么如果我們把 x 的地址傳遞給里面呢?下面這個例子:

          var?x?float64?=?3.4
          v?:=?reflect.ValueOf(&x)
          fmt.Println(v.CanSet())?//?false

          我們將 x 變量的地址傳遞給 reflect.ValueOf 了。應該是 CanSet 了吧。但是這里卻要注意一點,這里的 v 指向的是 x 的指針。所以 CanSet 方法判斷的是 x 的指針是否可以設置。指針是肯定不能設置的,所以這里還是返回 false。

          那么我們下面需要可以通過這個指針的 value 值來判斷的是,這個指針指向的元素是否可以設置,所幸 reflect 提供了 Elem() 方法來獲取這個“指針指向的元素”。

          var?x?float64?=?3.4
          v?:=?reflect.ValueOf(&x)
          fmt.Println(v.Elem().CanSet())?//?true

          終于返回 true 了。但是這個 Elem() 使用的時候有個前提,這里的 value 必須是指針對象轉換的 reflect.Value。(或者是接口對象轉換的 reflect.Value)。這個前提不難理解吧,如果是一個 int 類型,它怎么可能有指向的元素呢?所以,使用 Elem 的時候要十分注意這點,因為如果不滿足這個前提,Elem 是直接觸發(fā) panic 的。

          在判斷完是否可以設置之后,我們就可以通過 SetXX 系列方法進行對應的設置了。

          var?x?float64?=?3.4
          v?:=?reflect.ValueOf(&x)
          if?v.Elem().CanSet()?{
          ????v.Elem().SetFloat(7.1)
          }
          fmt.Println(x)

          更復雜的類型

          對于復雜的 slice, map, struct, pointer 等方法,我寫了一個例子:

          package?main

          import?(
          ?"fmt"
          ?"reflect"
          )

          type?Foo?interface?{
          ?Name()?string
          }

          type?FooStruct?struct?{
          ?A?string
          }

          func?(f?FooStruct)?Name()?string?{
          ?return?f.A
          }

          type?FooPointer?struct?{
          ?A?string
          }

          func?(f?*FooPointer)?Name()?string?{
          ?return?f.A
          }

          func?main()?{
          ?{
          ??//?slice
          ??a?:=?[]int{1,?2,?3}
          ??val?:=?reflect.ValueOf(&a)
          ??val.Elem().SetLen(2)
          ??val.Elem().Index(0).SetInt(4)
          ??fmt.Println(a)?//?[4,2]
          ?}
          ?{
          ??//?map
          ??a?:=?map[int]string{
          ???1:?"foo1",
          ???2:?"foo2",
          ??}
          ??val?:=?reflect.ValueOf(&a)
          ??key3?:=?reflect.ValueOf(3)
          ??val3?:=?reflect.ValueOf("foo3")
          ??val.Elem().SetMapIndex(key3,?val3)
          ??fmt.Println(val)?//?&map[1:foo1?2:foo2?3:foo3]
          ?}
          ?{
          ??//?map
          ??a?:=?map[int]string{
          ???1:?"foo1",
          ???2:?"foo2",
          ??}
          ??val?:=?reflect.ValueOf(a)
          ??key3?:=?reflect.ValueOf(3)
          ??val3?:=?reflect.ValueOf("foo3")
          ??val.SetMapIndex(key3,?val3)
          ??fmt.Println(val)?//?&map[1:foo1?2:foo2?3:foo3]
          ?}
          ?{
          ??//?struct
          ??a?:=?FooStruct{}
          ??val?:=?reflect.ValueOf(&a)
          ??val.Elem().FieldByName("A").SetString("foo2")
          ??fmt.Println(a)?//?{foo2}
          ?}
          ?{
          ??//?pointer
          ??a?:=?&FooPointer{}
          ??val?:=?reflect.ValueOf(a)
          ??val.Elem().FieldByName("A").SetString("foo2")
          ??fmt.Println(a)?//&{foo2}
          ?}
          }

          上面的例子如果都能理解,那基本上也就理解了 CanSet 的方法了。

          特別可以關注下,map,pointer 在修改的時候并不需要傳遞指針到 reflect.ValueOf 中。因為他們本身就是指針。

          所以在調(diào)用 reflect.ValueOf 的時候,我們必須心里非常明確,我們要傳遞的變量的底層結構。比如 map, 實際上傳遞的是一個指針,我們沒有必要再將他指針化了。而 slice, 實際上傳遞的是一個 SliceHeader 結構,我們在修改 Slice 的時候,必須要傳遞的是 SliceHeader 的指針。這點往往是需要我們注意的。

          CanAddr

          在 reflect 包里面可以看到,除了 CanSet 之外,還有一個 CanAddr 方法。它們兩個有什么區(qū)別呢?

          CanAddr 方法和 CanSet 方法不一樣的地方在于:對于一些結構體內(nèi)的私有字段,我們可以獲取它的地址,但是不能設置它。

          比如下面的例子:

          package?main

          import?(
          ?"fmt"
          ?"reflect"
          )

          type?FooStruct?struct?{
          ?A?string
          ?b?int
          }


          func?main()?{
          ?{
          ??//?struct
          ??a?:=?FooStruct{}
          ??val?:=?reflect.ValueOf(&a)
          ??fmt.Println(val.Elem().FieldByName("b").CanSet())??//?false
          ??fmt.Println(val.Elem().FieldByName("b").CanAddr())?//?true
          ?}
          }


          所以,CanAddr 是 CanSet 的必要不充分條件。一個 Value 如果 CanAddr, 不一定 CanSet。但是一個變量如果 CanSet,它一定 CanAddr。

          源碼

          假設我們要實現(xiàn)這個 Value 元素 CanSet 或者 CanAddr,我們大概率會相到使用標記位標記。事實也確實是這樣。

          我們先看下 Value 的結構:

          type?Value?struct?{
          ?typ?*rtype
          ?ptr?unsafe.Pointer
          ?flag
          }

          這里要注意的就是,它是一個嵌套結構,嵌套了一個 flag,而這個 flag 本身就是一個 uintptr。

          type?flag?uintptr

          這個 flag 非常重要,它既能表達這個 value ?的類型,也能表達一些元信息(比如是否可尋址等)。flag雖然是uint類型,但是它用位來標記表示。

          首先它需要表示類型,golang 中的類型有27個:

          const?(
          ?Invalid?Kind?=?iota
          ?Bool
          ?Int
          ?Int8
          ?Int16
          ?Int32
          ?Int64
          ?Uint
          ?Uint8
          ?Uint16
          ?Uint32
          ?Uint64
          ?Uintptr
          ?Float32
          ?Float64
          ?Complex64
          ?Complex128
          ?Array
          ?Chan
          ?Func
          ?Interface
          ?Map
          ?Ptr
          ?Slice
          ?String
          ?Struct
          ?UnsafePointer
          )

          所以使用5位(2^5-1=63)就足夠放這么多類型了。所以 flag 的低5位是結構類型。

          第六位 flagStickyRO: 標記是否是結構體內(nèi)部私有屬性 第七位 flagEmbedR0: 標記是否是嵌套結構體內(nèi)部私有屬性 第八位 flagIndir: 標記 value 的ptr是否是保存了一個指針 第九位 flagAddr: 標記這個 value 是否可尋址 第十位 flagMethod: 標記 value 是個匿名函數(shù)

          20201026181333

          其中比較不好理解的就是 flagStickyRO,flagEmbedR0

          看下面這個例子:


          type?FooStruct?struct?{
          ?A?string
          ?b?int
          }

          type?BarStruct?struct?{
          ?FooStruct
          }

          {
          ?????b?:=?BarStruct{}
          ????????val?:=?reflect.ValueOf(&b)
          ????????c?:=?val.Elem().FieldByName("b")
          ??fmt.Println(c.CanAddr())
          }

          這個例子中的 c 的 flagEmbedR0 標記位就是1了。

          所以我們再回去看 CanSet 和 CanAddr 方法

          func?(v?Value)?CanAddr()?bool?{
          ?return?v.flag&flagAddr?!=?0
          }

          func?(v?Value)?CanSet()?bool?{
          ?return?v.flag&(flagAddr|flagRO)?==?flagAddr
          }

          他們的方法就是把 value 的 flag 和 flagAddr 或者 flagRO (flagStickyRO,flagEmbedR0) 做“與”操作。

          而他們的區(qū)別就是是否判斷 flagRO 的兩個位。所以他們的不同換句話說就是“判斷這個 Value 是否是私有屬性”,私有屬性是只讀的。不能Set。

          應用

          在開發(fā) collection (https://github.com/jianfengye/collection)庫的過程中,我就用到這么一個方法。我希望設計一個方法 func (arr *ObjPointCollection) ToObjs(objs interface{}) error,這個方法能將 ObjPointCollection 中的 objs reflect.Value 設置為參數(shù) objs 中。

          func?(arr?*ObjPointCollection)?ToObjs(objs?interface{})?error?{
          ?arr.mustNotBeBaseType()

          ?objVal?:=?reflect.ValueOf(objs)
          ?if?objVal.Elem().CanSet()?{
          ??objVal.Elem().Set(arr.objs)
          ??return?nil
          ?}
          ?return?errors.New("element?should?be?can?set")
          }

          使用方法:

          func?TestObjPointCollection_ToObjs(t?*testing.T)?{
          ?a1?:=?&Foo{A:?"a1",?B:?1}
          ?a2?:=?&Foo{A:?"a2",?B:?2}
          ?a3?:=?&Foo{A:?"a3",?B:?3}

          ?bArr?:=?[]*Foo{}
          ?objColl?:=?NewObjPointCollection([]*Foo{a1,?a2,?a3})
          ?err?:=?objColl.ToObjs(&bArr)
          ?if?err?!=?nil?{
          ??t.Fatal(err)
          ?}
          ?if?len(bArr)?!=?3?{
          ??t.Fatal("toObjs?error?len")
          ?}
          ?if?bArr[1].A?!=?"a2"?{
          ??t.Fatal("toObjs?error?copy")
          ?}
          }

          總結

          CanAddr 和 CanSet 剛接觸的時候是會有一些懵逼,還是需要稍微理解下 reflect.Value 的 flag 就能完全理解了。

          參考文檔

          laws-of-reflectiongo addressable 詳解Go語言_反射篇

          - END -

          Hi,我是軒脈刃,一個名不見經(jīng)傳碼農(nóng),體制內(nèi)的小憤青,躁動的騷年,2020年想堅持寫一些學習/工作/思考筆記,謂之倒逼學習。歡迎關注個人公眾號:軒脈刃的刀光劍影。






          瀏覽 22
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  狠狠撸五月天 | 人人射影院 | A片日本免费看 | 国内超碰 | 天天肏|